剛剛在逛Stack的時候,看見有人在問Java下的一個浮點數運算的問題,這個問題我之前也碰到過,不過項目中遇見的比較少,就忘記了。想想還是做個筆記,記錄一下,以供后續溫習。
有趣的小例子
先做一道算術題0.1+0.2=?,也許你想都不用想就回答等於0.3,那么在計算機中是如何表現的呢?測試如下:
var a = 0.1 + 0.2; var b = 0.3; Console.WriteLine(a); Console.WriteLine(a == b);
輸出結果是:0.3和False,0.3!=0.3?且不說為什么,再測試一段:
float a = 0.7f; float b = 0.6f; Console.WriteLine(a - b);
輸出結果是:0.09999996,不等於0.1,我的機器抽風了?再試試:
float a = 0.75f; float b = 0.25f; Console.WriteLine(a - b);
輸出結果是:0.5,居然又正常了,頓時對C#有了一股深深的怨念,這點小事兒你都辦不好,不過想想那些大神的能力肯定甩我幾十條街,不會犯這種低級錯誤,還是看看為什么吧。
分析
究其根本,還是因為計算機采用二進制來表示十進制數據,C#中采用IEEE754標准來存儲浮點格式,單精度的浮點格式分為1位符號位、8位偏置指數位、23位小數位,通常,我們將十進制轉換為二進制需要進行如下操作:
將浮點型數據分為整數部分和小數部分,整數部分除2取余,得到的商再除以2取余,直到商等於0為止,然后把得到余數反序排列,就得到了整數部分;小數部分用乘2法不斷將整數部分取出,知道小數中的部分為0或者達到精度時為止。以4.25為例:
整數位:4
4/2=2 0
2/2=1 0
1/2=0 1
將001反序得到100即整數位4
小數位:0.25
0.25*2=0.5 0
0.5*2=1.0 1
01即表示0.25
可是當小數位為0.6時,推算如下:
0.6*2=1.2 1
0.2*2=0.4 0
0.4*2=0.8 0
0.8*2=1.6 1
0.6*2=1.2 1
0.2*2=0.4 0
0.4*2=0.8 0
0.8*2=1.6 1
......
如此循環往復,很明顯23位是無法完全表述0.6這個小數的,這也就導致了上訴異常的產生。
.NET中提供了decimal來解決這個問題,但decimal也是浮點數類型,只是精度更高,仍然有精度損失存在,所以,浮點數運算是一件非常危險的事情,還請慎重。