日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

c语言浮点数如何精确计算,浮点数精确运算的分析和解决办法

發(fā)布時間:2024/1/8 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c语言浮点数如何精确计算,浮点数精确运算的分析和解决办法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1.01 + 2.01 = 3.02

2.01 * 2.01 = 4 0401?? 不知你注意沒有,這個很尋常的等式,你如果將它放在C++中,Java中,Basic中,它

居然是不成立的。計算機在開玩笑嗎?噢,對了,隱約記得這好象是浮點數的問題,似乎

很多很多年前,老師說過。還有某位姓林的先生在某本書里提過=0的判斷。

嗯,如果你不遇到此問題,那你完全可以把它拋到火星上去,可惜,偶不好彩,這樣的

問題,被俺遇到了。唉!

why?how?

沒辦法,硬著頭皮,從頭開始。

一:為何不成立?Why?

這得從浮點數的在計算機內的存儲開始說起,我這里閑話少說。我們只談雙精度double

數(至于float,基本上是五十步和一百步的區(qū)別)。

雙精度數在計算機內的表示方式是:(三部分組成)

符號(正或負)? 階碼(2的N次冪)?? 尾數(大于等于1小于2的數)

比如: -(符號) 1.01(尾數) * 2~1(N = 1)? = - 2.02

具體到計算機的存儲單元:雙精度數共占8字節(jié)(64bit)

符號位(占1個bit) 階碼(11個bit) 尾數(52個bit)

解釋一下:

符號位:0表示正 1表示負

階碼:是一個偏移量,1023的偏移量,它的1023相當于0,小于1023時為負,

大于1023時為正,如:10000000001表示指數為1025 - 1023 = 2,表示真值為2^2。

好了,知道了原理,我們開始分析上述等式為何為不等。

(相應數的存儲值,可以簡單用C語言的指針方式取出)

1.01 表示為:

0?? 0111111 1111??? 0000 00101000 11110101 11000010 10001111 01011100 00101001

2.01 表示為;

0?? 1000000 0000??? 0000 00010100 01111010 11100001 01000111 10101110 00010100

3.02 表示為:

0?? 1000000 0000??? 1000 00101000 11110101 11000010 10001111 01011100 00101001

2.01+1.01 在編程語言中的計算結果 表示為:

0?? 1000000 0000??? 1000 00101000 11110101 11000010 10001111 01011100 00101000

好了,我們可以比較一下3.02和計算結果,果然有所不同,只不過最后一個bit不同嘿。

為了驗證一下,可以用手工計算一下2.01+1.01:

先把1.01的冪次變?yōu)?(與2.01的階碼相同),于是,將尾數右移一位。得到:

1000 00010100 01111010 11100001 01000111 10101110 000101001

加上2.01的尾數。

0000 00010100 01111010 11100001 01000111 10101110 00010100

得到:

1000 00101000 11110101 11000010 10001111 01011100 00101000

嗯,與計算機的計算結果相同,我們的運算思路是正確的。

因此,結論出來了,因為浮點數在計算機內的存儲存在偏差,導致運算時,與實際期望的結

果不同。很多時候,你可以不理它,但是,可以肯定負責任的說,發(fā)射衛(wèi)星的運算時,你

需要知道,否則,衛(wèi)星一轉眼就不見了。

二:不成立的的原因找到了,那怎么解決這個問題呢,How?

一個簡單的解決辦法是:

不要用浮點數來存儲浮點,對于VC,Java,Basic,最好的辦法是用Decimal來保存它。

下面是分別的實現:(以加法為例,其它四則運算處理相同)

VC中:

double doublAdd(double dbl1, double dbl2)

{

double dblResult;

DECIMAL dec1,dec2,decResult;

::VarDecFromR8(dbl1,&dec1);

::VarDecFromR8(dbl2,&dec2);

::VarDecAdd(&dec1,&dec2,&decResult);

::VarR8FromDec(&decResult,&dblResult);

return dblResult;

}

VB中:

Private Function doubleAdd(ByVal dbl1 As Double, ByVal dbl2 As Double) As Double

doubleAdd = CDec(dbl1) + CDec(dbl2)

End Function

Java中:

public static double add(double v1, double v2) {

BigDecimal b1 = new BigDecimal(Double.toString(v1));

BigDecimal b2 = new BigDecimal(Double.toString(v2));

return b1.add(b2).doubleValue();

}

解決思路就是:用其它精確的表示法來存儲浮點數,就這么簡單。

注意:VC示例中,VarDecFromR8是做了手腳地,如果能直接用VarDecFromStr那更好。

三:在C/C++中,似乎很不情愿看到類似上例中的代碼,因為它看起來很低效,還有其它方法嗎?

好象還有,對了,只是好象。

我們再來看看雙精度數的表示法:

尾數一共有52個bit,也就是最小能表示的數是 2^-52,取對數可得出,約是

在小數點后16位,那也就是說小數點后15位是可以精確表示的,加上前置的默認1,一共有16位

數字是精確可靠的。我們來試驗一下,看上述結論是否成立。

看看VC調試器的顯示值。

2.01 的顯示值: 2.0099999999999998

如果只取16位有效數字,那么將最后一位8四舍五入,我們得到正確的表示。

好了,這能說明什么呢?

四:我們先看比較簡單的加,減法運算。

對于加法:dbl1 + dbl2:

假設dbl1=1.01 那么,16減去整數位1,我們可以假定,在計算機表示中:

小數點后的15位都是精確的。

假設dbl2=100.01 那么 16-3,假定小數點后13位是精確的。

憑經驗我們可以知道,兩個小數相加,小數點后的精度不會大于精度銷大的一個。

所以,我們判定得出結果的精確度可以用較大的一個為準。

于是,將得出的結果,去掉不精確的位數,則應該可以得到準確值。

VC實現如下:

#define DELTA_RATE? 16

int getRound(double dbl)

{

COleVariant var(dbl);

COleVariant varForLog(dbl);

::VarRound(&varForLog,0,&varForLog);

int nIntCount = log10(varForLog.dblVal>0?varForLog.dblVal:-varForLog.dblVal) + 1;

int nRound = DELTA_RATE - nIntCount;

return nRound;

}

double doublAdd2(double dbl1, double dbl2)

{

COleVariant var(dbl1+dbl2);

int r1 = getRound(dbl1);

int r2 = getRound(dbl2);

::VarRound(&var,max(r1,r2),&var);

return var.dblVal;

}

做過一些實驗,好象是正確的。同理可以實現doubleSub2的函數。

注意:這里并不用下面五所提的取精度的方式,因為取精度的運算更低效。

五:對于乘除法呢?問題有些復雜,先找出一個需要處理的例子。

如:2.01*2.01=4.0401。

試了一下,不成立。

用方法一的Decimal方式測試,可以通過。

那么方法二呢?

再做假設吧,假設dbl1有兩位小數,dbl2也有兩位小數,按理論,

可得出相乘后,最大可能是2+2位小數。那么,我們按照 4位小數

進行Round處理,可能會得出正確的結果。

實際上,要取一個雙精度的10進制表達的小數位,我沒有找到什么好辦法,

我能想到的:也就是將數字轉為字串,然后查找.后的位數。這樣,顯然是

非常低效的,這里,我就不再寫出代碼了。

六:比較方法一和方法二。方法二并不高效,并且還有一些不定因素,所以,

最好采用方法一來統(tǒng)一處理浮點數的運算。

至于效率,實際上最佳方法是從程序的設計著手,將double從程序中去除掉。

比如在VC中,可以用Variant::Decimal來徹底替換double,這樣,就不存在

中間的轉換了,效率自然就提高了。有關Decimal的常用函數是:

VarDecFromStr VarDecAdd VarDecSub VarDecMul VarDecDiv ……

VarBstrFromDec

至于Java和VB,也可以方便的找到相應函數。

很想找到一種更好的方法,總覺得用Decimal來進行運算很不爽,但真的沒找到?

其實呢,做了一下測試,Decimal的運算并不慢,如果可以將內部存儲改為Decimal,

那就可以徹底解決問題了。

總結

以上是生活随笔為你收集整理的c语言浮点数如何精确计算,浮点数精确运算的分析和解决办法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。