前言
學java的肯定都知道,要保證小數運算精度不丟失我們得用BigDecimal
物件。這篇文章就分析一下為什麼用浮點數會造成精度丟失?BigDecimal
是怎麼解決精度丟失問題的?下面我們一起看看吧!
浮點數的表示
浮點數在計算機中通常採用 IEEE 754 標準進行表示。這個標準將數值分為三個部分:符號位、指數部分和尾數部分。由於尾數的位數有限,某些小數(尤其是十進制小數)無法精確地用二進制表示。
精度丟失案例
public class FloatPrecision { public static void main(String[] args) { double a = 0.1; double b = 0.2; double c = a + b; System.out.println(c); // 輸出 0.30000000000000004 } }
在這個例子中,儘管我們期望 c
的值為 0.3,但實際上它被計算為 0.30000000000000004。這種現象在浮點數運算中非常常見,尤其是在涉及多個浮點數的加減乘除時,誤差可能會逐漸累積。
為什麼浮點數不能精確表示小數呢?
瞭解了10進位制小數轉二進制過程,就知道為啥呢?
十進制小數轉換成二進制小數採用"乘2取整,順序排列法:
● 用2乘十進制小數,可以得到積
● 將積的整數部分取出,再用2乘餘下的小數部分,又得到一個積
● 再將積的整數部分取出,如此進行,直到積中的小數部分為零,此時0或1為二進制的最後一位。或者達到所要求的精度為止。
0.00011001100110011...(迴圈)
BigDecimal
的解決方案
透過BigDecimal("0.1")
的String 構造不會造成精度丟失,避免建構函式用浮點數表示,上面已經說過了浮點數本身存在精度丟失的問題。BigDecimal
是怎麼解決精度丟失的呢?
BigDecimal
是透過一個"無標度值"和一個"標度"來表示一個數的。
關鍵得三個引數:
無標度值(Unscaled Value):這是一個整數,表示BigDecimal的實際數值。
標度(Scale):這是一個整數,表示小數點後的位數。
BigDecimal的實際數值計算公式為:unscaledValue × 10^(-scale)。
假設有一個BigDecimal表示的數值是123.45,那麼無標度值(Unscaled Value)是12345。標度(Scale)是2。因為123.45 = 12345 × 10^(-2)。
當標度為正數時,它表示小數點後的位數。例如,在數字123.45中,他的無標度值為12345,標度是2。
當標度為零時,BigDecimal表示一個整數。
當標度為負數時,它表示小數點向左移動的位數,相當於將數字乘以 10 的絕對值的次方。
例如,一個數值為1234500,那麼他可以用value是12345,scale為-2來表示,因為1234500 * 10^(-2) = 12345。
(當需要處理非常大的整數時,可以使用負數的標度來指定小數點左側的位數。這在需要保持整數的精度而又不想丟失尾部零位時很有用。)
不能用BigDecimal的equals方法做等值
直接上原始碼吧:
public boolean equals(Object x) { if (!(x instanceof BigDecimal)) { return false; } else { BigDecimal xDec = (BigDecimal)x; if (x == this) { return true; //比較標度 } else if (this.scale != xDec.scale) { return false; } else { long s = this.intCompact; long xs = xDec.intCompact; if (s != -9223372036854775808L) { if (xs == -9223372036854775808L) { xs = compactValFor(xDec.intVal); } return xs == s; } else if (xs != -9223372036854775808L) { return xs == compactValFor(this.intVal); } else { return this.inflated().equals(xDec.inflated()); } } } }
equals
方法會比較標度,所以比較大小的話用 compareTo()
比較吧
import java.math.BigDecimal; public class BigDecimalComparison { public static void main(String[] args) { BigDecimal num1 = new BigDecimal("1.0"); BigDecimal num2 = new BigDecimal("1.00"); System.out.println("equals: " + num1.equlse(num2)); // 輸出false System.out.println("compareTo: " + num1.compareTo(num2)); // 輸出: 0 } }
小結
因為某些十進制的小數在二進制中是一個無限迴圈的小數,所有用浮點數存在進度丟失問題。 BigDecimal
採用標度的形式解決了精度丟失問題。同時在使用BigDecimal
的時候,使用String
的構造器。 最後在使用BigDecimal
比較大小的時候不要使用equals
,請用compareTo