Java 中的雙精確度問題
1. 概述
在處理浮點數時,我們經常遇到稱為雙精確度問題的捨入誤差。
在這個簡短的教程中,我們將了解導致此類問題的原因、它如何影響我們的程式碼以及如何處理它。
2. 浮點數
在深入討論之前,我們先簡單討論一下浮點數的工作原理。現在,在電腦世界中,它們使用IEEE 754 標準來表示。它是定義將實數轉換為二進位格式的方法的標準。
浮點數使用二進位表示形式,不能總是精確地表示十進制數字。眾所周知,Java 在處理浮點數時提供了兩種基本資料類型: float
和double
。兩種類型都具有有限精度, float
型為 32 位, double
精度型為 64 位。
根據標準,雙精度資料類型的表示由三個部分組成:
- 符號位 – 包含數字的符號(1 位元)
- 指數 – 控制數字的比例(11 位元)
- 分數(尾數)-包含數字的有效數字(52 位)
3. 雙精度問題
現在,為了理解雙精確度問題,讓我們執行兩個十進制數的簡單加法:
double first = 0.1;
double second = 0.2;
double result = first + second;
使用基本數學,我們預期結果為 0.3。但是,如果我們運行程式碼,我們會看到實際結果有所不同:
assertNotEquals(0.3, result);
assertEquals(0.30000000000000004, result);
此舍入誤差背後的問題在於浮點數的二進位表示形式。
由於位數是固定的,因此某些十進制數字(例如0.1
)無法使用二進位格式準確表示。
作為範例,我們使用 IEEE 754 標準編寫0.1
值。我們可以使用Float Exposed 、 Float Toy或IEEE 754 視覺化等工具來查看該值的二進位格式是什麼樣的。
這是數字0.1
從十進制轉換為 IEEE 754 二進位:
0 - 01111111011 - 1001100110011001100110011001100110011001100110011001
在這裡,我們看到“0011”序列在值的尾數部分重複。而且,相同的序列在末尾被截斷,表明該數字以二進位格式表示為無限數。
不幸的是,我們無法在程式碼中保留無限的數字。因此,必須對數字進行舍入以適合其有限的二進位表示形式。
因此,在執行計算時,計算機不會使用數字的完整二進位表示形式。因此,我們在算術計算過程中會看到捨入誤差。
值得注意的是,並非所有浮點數都會產生捨入誤差。不產生錯誤的值是具有有限二進位表示形式的值。
4. 處理雙精確度問題
我們可以透過合併BigDecimal
等提供更高精度和準確度的類別來避免雙精度問題。
現在,讓我們執行相同的加法,但這次使用BigDecimal
而不是double
類型:
BigDecimal first = BigDecimal.valueOf(0.1);
BigDecimal second = BigDecimal.valueOf(0.2);
BigDecimal result = first.add(second);
assertEquals(BigDecimal.valueOf(0.3), result);
與前面的範例相反,這裡我們得到的預期結果是 0.3。
5. 結論
在這篇短文中,我們了解了雙精確度問題是什麼以及如何處理它。
綜上所述,出現舍入誤差是由於用於表示浮點數的 IEEE 754 標準所造成的。在處理這個問題時,我們可以使用像BigDecimal
這樣提供高精度的類型。