一、前言
在大家的認(rèn)知過程中可能會認(rèn)為計算機(jī)是不會出現(xiàn)計算錯誤的,但是實際上,依然存在程序運(yùn)行后無法得到正確數(shù)值的情況。其中,最經(jīng)典的就是小數(shù)運(yùn)算。(做金融的一定要小心?。。。?br>
二、引入
在我們的世界里面,100個0.1相加就是10,這個是沒有疑問的。但是當(dāng)我們用C語言如下的程序來計算的時候,結(jié)果并非是10(不同語言計算的結(jié)果可能不同,這里主要說C)。
首先是一段計算代碼:

運(yùn)行結(jié)果如下:
10.000002
計算機(jī)通過編譯、鏈接、運(yùn)行得到的結(jié)果是10.000002。程序沒有錯?,F(xiàn)在讓我們來看一下具體原因吧。
三、計算機(jī)計算結(jié)果不正確的原因
簡單來說,就是無法表示正確的數(shù)值,導(dǎo)致計算出來的結(jié)果成了近似值。下面進(jìn)一步剖析一下。
首先,我們來看一下在計算機(jī)世界里面如何用二進(jìn)制數(shù)表示小數(shù):
例如把1011.0011這個小數(shù)點的二進(jìn)制數(shù)轉(zhuǎn)成十進(jìn)制數(shù)。(只需將各數(shù)位數(shù)值和位權(quán)相乘,然后將相乘的結(jié)果相加)
也就是:12^3+022+1*21+12^0+02(-1)+0*2(-2)+12^(-3)+12^(-4) = 11.1875。
了解了二進(jìn)制表示的小數(shù)轉(zhuǎn)十進(jìn)制的方法后,計算出錯的原因也就容易理解了。用小數(shù)點后4位用二進(jìn)制表示時的數(shù)值范圍為:0.0000~0.1111。因此,對應(yīng)的十進(jìn)制結(jié)果如下:

從上面的對照表可以看出,0的下一位就是0.625。因此0~0.0625之間的數(shù)值計算機(jī)無法用小數(shù)點后4位數(shù)的二進(jìn)制數(shù)表示。因此可以看出0.1無法用4位二進(jìn)制數(shù)表示。就算增加二進(jìn)制的位數(shù),也無法得到2^(-x) =0.1 這個結(jié)果。
實際上,十進(jìn)制0.1轉(zhuǎn)成二進(jìn)制后,就變成了0.0001100110011……(1100循環(huán))這樣的循環(huán)小數(shù)。就像1/3是一個道理。因此100各0.1相加不等于10,而是等于近似值。
---------------------------------------------以上就能夠回答標(biāo)題的原因了---------------------------------------------
四、What is 浮點數(shù)?
其實像剛才那樣的1011.0011這種表現(xiàn)形式完全是紙面上的二進(jìn)制數(shù)表現(xiàn)形式,在計算機(jī)內(nèi)部是無法使用的(計算機(jī)內(nèi)部只是0101001……沒有"."這個概念)。實際上,編程語言提供了雙精度浮點數(shù)(double)和單精度浮點數(shù)(float)。雙精度浮點數(shù)類型用64位、單精度浮點數(shù)用32位來表示全體小數(shù)。
浮點數(shù):就是用符號、尾數(shù)、基數(shù)和指數(shù)表示的小數(shù)。

其中:±表示符號,m表示尾數(shù),n表示基數(shù),e表示指數(shù)。實際數(shù)據(jù)中不考慮基數(shù)。因此:

其中:
1、符號部分:1表示負(fù)、0表示正或者0。
2、尾數(shù)部分用的是:將小數(shù)點前面的值固定位1的正則表達(dá)式。
3、指數(shù)部分:用的是EXCESS系統(tǒng)表現(xiàn)。
先看看尾數(shù)部分。對于十進(jìn)制的0.75。我們有如下的表示方法:
①、0.75 = 0.7510^0
?、凇?.75 = 7510^(-2)
?、邸?.75 = 0.075*10^1
十進(jìn)制的表示正則為:小數(shù)點前面是0,小數(shù)點后面第一位不是0的規(guī)則表示。而對于二進(jìn)制也是一樣的道理,使用的是:將小數(shù)點前面的值固定為1的正則。也就是將二進(jìn)制數(shù)表示的小數(shù)左移或右移(邏輯移位)數(shù)次后,整數(shù)部分的第一位變成1,第二位之后變成0.而且第1位的1在實際數(shù)據(jù)中不保存。
例如1011.0011:
移位變成0001.0110011,確保小數(shù)點后23位:0001.01100110000000000000000,僅保留小數(shù)點后面完成正則:01100110000000000000000。
再看看指數(shù)部分。EXCESS系統(tǒng)表現(xiàn):將指數(shù)部分表示范圍的中間值設(shè)置為0,使得負(fù)數(shù)不需要用符號來表示。例如當(dāng)指數(shù)部分是8為單精度浮點時,最大值11111111=225的1/2即01111111=127表示0。雙精度類似。
因此對于單精度浮點數(shù)的表現(xiàn),其表示范圍就是:0000000011111111也就是-127128??聪旅胬?

運(yùn)行結(jié)果:
0-01111110-10000000000000000000000
其中01111110是126,EXCESS表示為-1。
小數(shù)點前面的第一位是1。因此尾數(shù)就是:1.10000000000000000000000也就是1.5。
也就是+1.5*2^(-1) = 0.75。
五、如何避免小數(shù)計算出錯導(dǎo)致的問題
可以將小數(shù)替換成整數(shù)來計算。然后在縮小相應(yīng)的倍數(shù)。
注:
1、如果有什么Bug或者說的不對的地方,歡迎大家隨時提建議或者意見。