计算机原理-浮点数存储
1、精度丟失
作為程序員大家應該都遇到過下面這種情況,用浮點數做運算,發現結果與預期有偏差,比如下面的JAVA代碼
public static void main( String[] args ){int i = 3;float j = 0.9f;System.out.println("3乘以0.9的結果是:" + i*j);}用一個整數3乘以浮點數0.9,期望結果是2.7,實際結果卻是
與2.7相差0.0000002,這道連小學生都不會算錯的題目,為什么計算機會算錯?真正的原因要從計算機保存浮點數的底層原理說起。
2、計算機如何保存浮點數
2.1、二進制小數
在計算機中數字使用二進制方式存儲,所以理解浮點數的第一步是理解如何用二進制表示小數。
首先,對于我們比較了解的十進制,可以使用下面的公式表示:
dmdm-1···d1d0.d-1d-2···d-n
d的取值范圍是0-9的任意數字。
m則從左向右依次遞減,在小數點左邊m=0,小數點右邊m=-1。
在小數點左邊的數字,取10的m次正冪,得到整數。小數點右邊則取10的m次負冪,得到小數。例如12.34 表示數字
1×101+2×100+3×10?1+4×10?2=12341001×101+2×100+3×10?1+4×10?2=1234100
同樣的,二進制小數也可以用類似方式表示,b的取值范圍變成了0-1,小數點左邊計算2的m次正冪,右邊計算2的m次負冪。例如101.11 表示數字
1×22+0×21+1×20+1×2?1+1×2?2=5341×22+0×21+1×20+1×2?1+1×2?2=534
但是這種表示方法并不完美,存在兩個缺陷。
1) 無法準確的表示所有數字。
例如十進制小數0.20,我們無法通過二進制小數來準確表示,只能不斷增加二進制長度來提高精度。
| 0.0 | 0202 | 0.0 |
| 0.01 | 1414 | 0.25 |
| 0.0011 | 316316 | 0.1875 |
| 0.001101 | 13641364 | 0.203125 |
| 0.00110011 | 5125651256 | 0.19921875 |
從上圖可以看到,數字的值在不斷的接近0.20,但是始終存在偏差。
2) 無法有效表示非常大的數字。
例如表達式 5×2^100 是二進制101后面跟了100個0,如果用上面的方式表示非常浪費空間。
因此,人們需要一種更加簡潔的方式來表示浮點數。1985年,Intel公司贊助美國加州大學的William Kahan教授,設計了一套處理器浮點數標準,這個標準最終被IEEE(電氣和電子工程師協會)借鑒,制定出IEEE浮點標準。目前,所有的計算機都支持這個標準。
2.2、IEEE 浮點標準
根據IEEE 浮點標準,任意一個二進制浮點數V可以表示成下面的形式:
V = (-1)^s × 2M × 3^E
比如十進制的11.0,寫成二進制就是1011.0,用IEEE標準表示就是(-1)^0 × 1.011 × 2^3 ,s=0,M=1.011,E=3。
那么,計算機是如何存儲s,M,E這三個值呢?如果是一個單精度(32位)浮點數,計算機會在內存中開辟一個32位的存儲空間,最高1位保存s,中間8位保存E,最后23位保存M。
如果是一個雙精度(64位)浮點數,則開辟64位的存儲空間,最高1位保存s,中間11位保存E,最后52位保存M。
對于符號s,存儲值非0即1。
對于尾數M,只保存后面的小數部分。這是由于1≤M<2,在計算機內部保存M時,默認這個數的第一位總是1,因此可以被舍去,這樣做的好處是可以節省一位有效數字。
而對于階碼E,情況較為復雜。
首先,E要通過中間值換算得到真實值。這是由于E要能夠表示負數,也就是負次冪。而E本身是無符號的,因此IEEE浮點標準規定,在計算真實值時,E要減去一個中間值。單精度情況下,E減去127,雙精度情況下,E減去1023。
以二進制數1011.0為例,E的真實值是3,但是存儲在計算機中是3+127=130(單精度),換算成二進制就是10000010。
另外,當E的值不同時,對最終結果計算方法也不一樣,一共有下面三種情況。
1) 當E不全為0,也不全為1時。
表示規格化形式的數字。此時E減去中間值得到真實值,M的整數部分取1。
2) 當E全為0時。
表示非規格化形式的數字,主要是0或者非常接近于0的數。此時E減去中間值得到真實值,M的整數部分取0。
3) 當E全為1時。
表示特殊值。如果M全為0,表示±無窮大(正負取決于符號s),如果M不全為0,表示這不是一個數(NaN)。
3、精度丟失的原因
了解了原理,現在我們可以開始分析精度丟失的原因,下面我們來模擬計算機的運算過程。
- 第一步,將十進制結果轉換成二進制。文章開頭的例子中,十進制結果是2.7,由于2.7無法用二進制精確表示,因此出現第一次精度丟失。
2.7 => 10.10110011001…
- 第二步,用IEEE標準表示二進制浮點數,得到s=0,M=1.010110011001…,E=1。
10.1011001… => (-1)^0 × 1.01011001… × 2^1
- 第三步,按照IEEE標準保存數據。此時是單精度浮點數,M只能保存小數點后23位,多余的部分被丟棄了,因此出現第二次精度丟失。下面是計算機中實際保存的結果。
| 0 | 10000000 | 01011001100110011001100 | 11001… |
* 第四步,從內存中取出二進制數值,還原成十進制后顯示在屏幕上。由于前面已經經歷了兩次精度丟失,因此還原出來的結果也就不正確了。
4、如何避免精度丟失
使用整數替代浮點數。二進制整數可以完整的表示所有十進制整數,不存在精度丟失問題,因此我們可以將小數位數固定或者較少的數字轉換成整數存儲。比如存儲貨幣金額,如果存儲單位是元,則需要保留兩位小數,例如23.45元。如果將單位改成分,則可以完全使用整數存儲,例如2345分。
使用特殊類處理高精度運算。例如JAVA中的Bigdecimal類。不過要注意,使用這些特殊類雖然可以解決精度問題,但有可能帶來其它問題,JAVA中的Bigdecimal類在處理性能上就比float和double要低很多。
博文地址
https://www.taowong.com/blog/2018/07/10/principle-of-computer-float-num.html
總結
以上是生活随笔為你收集整理的计算机原理-浮点数存储的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot 在线调整日志级别
- 下一篇: 国内外关于文物安全的法律法规、政策、标准