使用構造方法 BigDecimal(double) 的方式把 double 值轉化為 BigDecimal 對象造成精度損失。
說明:BigDecimal(double)存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。
如:BigDecimal g = new BigDecimal(0.1f); 實際的存儲值為:0.10000000149
正確的示例:
// 優先推薦入參為 String 的構造方法,或使用 BigDecimal 的 valueOf 方法,此方法內部其實執行了Double 的 toString,而 Double 的 toString 按 double 的實際能表達的精度對尾數進行了截斷。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals來判斷。
錯誤的示例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
// 預期進入此代碼快,執行其它業務邏輯
// 但事實上 a==b 的結果為 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 預期進入此代碼快,執行其它業務邏輯
// 但事實上 equals 的結果為 false
}
正確的示例:
// 指定一個誤差范圍,兩個浮點數的差值在此范圍之內,則認為是相等的。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
// 使用 BigDecimal 來定義值,再進行浮點數的運算操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
Math.abs(x)<1e-6其實相當於x == 0
1e-6(也就是0.000001)叫做epslon,用來抵消浮點運算中因為誤差造成的相等無法判斷的情況。它通常是一個非常小的數字(具體多小要看你的運算誤差)
比如說因為精度誤差,用十進制舉例,我們要算1/3+1/3+1/3 == 1(從數學上說,肯定相等),但是因為精度問題,等號左邊算> 出來是0.3333333+0.3333333+0.3333333 = 0.9999999,存在了誤差,右邊是1.0000000,那么如果直接用 == ,返回false,我們> > 希望它被視作相等。那么就要兩數相減取絕對值小於epslon的辦法。
BigDecimal運算
BigDecimal a = new BigDecimal(2);
BigDecimal b = new BigDecimal(3);
BigDecimal c = new BigDecimal(4);
int n = 3;
- 1、加法運算 a + b 用 add()
BigDecimal d = a.add(b);
運行結果:5
- 2、減法運算 a - b 用 subtract()
BigDecimal d = a.subtract(b);
運行結果:-1
- 3、乘法運算 a * b 用 multiply()
BigDecimal d = a.multiply(b);
運行結果:6
- 4、除法運算 a / b 用 divide()
BigDecimal d = a.divide(b);
這里運行的時候我們會發現拋出異常了:at java.math.BigDecimal.divide(Unknown Source)
是因為2/3 無窮盡,所以拋出了異常,這里我們需要確定需保留的小數長度:
BigDecimal d = a.divide(b,2,BigDecimal.ROUND_HALF_UP);
運行結果:0.67
這里的2是需保留的小數長度;BigDecimal.ROUND_HALF_UP是設定該舍入方式為四舍五入
- 5、冪運算 a ^ n 用 pow()
BigDecimal d = a.pow(n);
運行結果:8
- 6、比較大小 a < b 用 compareTo()
int e = a.compareTo(b);
運行結果:-1
如果 a < b ; 返回 -1;
如果 a = b ; 返回 0;
如果 a > b ; 返貨 1;
- 8、設定精度用 setScale()
BigDecimal e = a.setScale(2,BigDecimal.ROUND_HALF_UP);
運行結果:2.00
BigDecimal的舍入模式
- 1、ROUND_UP
舍入遠離零的舍入模式。
在丟棄非零部分之前始終增加數字(始終對非零舍棄部分前面的數字加1)。
注意,此舍入模式始終不會減少計算值的大小。
- 2、ROUND_DOWN
接近零的舍入模式。
在丟棄某部分之前始終不增加數字(從不對舍棄部分前面的數字加1,即截短)。
注意,此舍入模式始終不會增加計算值的大小。
- 3、ROUND_CEILING
接近正無窮大的舍入模式。
如果 BigDecimal 為正,則舍入行為與 ROUND_UP 相同;
如果為負,則舍入行為與 ROUND_DOWN 相同。
注意,此舍入模式始終不會減少計算值。
- 4、ROUND_FLOOR
接近負無窮大的舍入模式。
如果 BigDecimal 為正,則舍入行為與 ROUND_DOWN 相同;
如果為負,則舍入行為與 ROUND_UP 相同。
注意,此舍入模式始終不會增加計算值。
- 5、ROUND_HALF_UP
向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則為向上舍入的舍入模式。
如果舍棄部分 >= 0.5,則舍入行為與 ROUND_UP 相同;否則舍入行為與 ROUND_DOWN 相同。
注意,這是我們大多數人在小學時就學過的舍入模式(四舍五入)。
- 6、ROUND_HALF_DOWN
向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則為上舍入的舍入模式。
如果舍棄部分 > 0.5,則舍入行為與 ROUND_UP 相同;否則舍入行為與 ROUND_DOWN 相同(五舍六入)。
- 7、ROUND_HALF_EVEN
向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。
如果舍棄部分左邊的數字為奇數,則舍入行為與 ROUND_HALF_UP 相同;
如果為偶數,則舍入行為與 ROUND_HALF_DOWN 相同。
注意,在重復進行一系列計算時,此舍入模式可以將累加錯誤減到最小。
此舍入模式也稱為“銀行家舍入法”,主要在美國使用。四舍六入,五分兩種情況。
如果前一位為奇數,則入位,否則舍去。
以下例子為保留小數點1位,那么這種舍入方式下的結果。
1.15>1.2 1.25>1.2
- 8、ROUND_UNNECESSARY
斷言請求的操作具有精確的結果,因此不需要舍入。
如果對獲得精確結果的操作指定此舍入模式,則拋出ArithmeticException。