為何二進位浮點數不準確?IEEE 754 二進位浮點數拾入規則
本節使用的“不準確”一詞可能會被解釋為“有效位數丟失”,如果從二進位的角度來講,一些無限小數的數位的確丟失了,但從開發人員的角度看,數值更像是失去了準確性。
二進位浮點數不準確
二進位浮點數存在不準確的情況,尤其在計算的結果中,你可能會看到意想不到的數值。讓我們來看一個經典的案例,在 JavaScript 中計算0.1
與0.2
的和,輸出結果並不是理所當然的0.3
,而是0.30000000000000004
。
console.log(`0.1 + 0.2 = ${parseFloat('0.1') + parseFloat('0.2')}`)
0.1 + 0.2 = 0.30000000000000004
上面的範例說明,浮點數0.1
,0.2
,0.3
,至少有一個是不準確的,當然,事實上他們每一個都無法準確。
二進位浮點數不準確的原因
二進位浮點數之所以會不準確,是因為我們習慣使用十進位,而十進位與二進位的數值並不是一一對應的,大部分十進位小數沒有對應的二進位小數。
如果十進位數值只有整數部分,那麽沒有問題,你總能找到一個相同的二進位數值,只需要不斷的加1
或減1
。
可一旦擁有小數部分,情況就變得完全不同了。如果把小數理解為對物體的劃分,那麽0.3
相當於把物體分為 10 份然後取走其中的 3 份,這樣的事情在二進位中是無法做到的,因為二進位的小數只能將物體分為 2 份,每一份都對應了十進位的0.5
,雖然可以將 2 份劃分為 4 份,甚至更進一步的劃分下去,但最終你會發現,二進位沒有等價於十進位0.3
的小數,他只能無限的接近0.3
,這也就是二進位浮點數不準確的原因。
最低位為 5 的十進位小數擁有對應的二進位小數
大部分十進位小數都找不到對應的二進位小數,除了以 5 結尾的十進位小數以外,比如十進位的0.5
對應了二進位0.1
。這可以被解釋為,取出 10 份中的 5 份,與取出 2 份中的 1 份是等價的。
都怪人們不使用 0 和 1?!
當然,你也可以把不準確的原因,歸於人們不習慣使用由0
和1
組成的二進位數值,一旦沒有了十進位和二進位之間的轉換,也就沒有了不準確的問題。
IEEE 754 二進位浮點數拾入規則
由於儲存空間有限,對於從十進位轉換而來的二進位無限小數,IEEE 754 標準規定需要對其進行拾入,以便能夠放入儲存空間內。
IEEE 754 二進位浮點數就近拾入規則
IEEE 754 定義了四種拾入方式,預設采用就近拾入的方式,他類似於十進位的四舍六入五取偶。當被舍棄部分的最高位為0
時不需要進1
,這相當於四舍。當被舍棄部分的最高位為1
並且後面不全為0
時需要進1
,這相當於六入。當被舍棄部分的最高位為1
且後面全為0
時,則根據未舍棄部分的最低位來決定是否進1
,最低位為1
,需要進1
,最低位為0
,不需要進1
,這相當於五取偶。
假設,二進位小數0.000011000
被舍棄的部分是末尾的1000
,那麽需要進1
,因為未舍棄部分的最低位為1
,拾入結果將是0.00010
。
IEEE 754 二進位浮點數向零拾入,向正無限大拾入,向負無限大拾入規則
除了就近拾入規則,IEEE 754 還定義了向零拾入,向正無限大拾入,向負無限大拾入,他們可用於計算範圍邊界。
向零拾入會直接丟棄需要舍棄的部分,這樣無論是正數還是負數都會更接近於0
。
向正無限大拾入會直接丟棄負數需要舍棄的部分,如果正數被丟棄的部分含有1
則進1
,否則同樣直接丟棄,這將保證無論是正數還是負數都會更接近正無限大。
向負無限大拾入會直接丟棄正數需要舍棄的部分,如果負數被丟棄的部分含有1
則進1
,否則同樣直接丟棄,這將保證無論是正數還是負數都會更接近負無限大。