Java使用BigDecimal解決精度丟失問題
一、先搞懂:為什么浮點型會丟精度?
我們常用的float和double,屬于二進制浮點類型,但現(xiàn)實中我們計算的“金額(如1.2元)”“重量(如3.5kg)”是十進制數(shù)據(jù)——這兩種進制的“不兼容”,就是精度丟失的根源。
舉個最直觀的例子,運行這段代碼:
public class FloatTest {
public static void main(String[] args) {
System.out.println(0.1 + 0.2); // 輸出結果不是0.3,而是0.30000000000000004
System.out.println(1.0 - 0.9); // 輸出0.09999999999999998
System.out.println(2.01 * 100); // 輸出200.99999999999997
}
}
為什么會這樣?
因為0.1在二進制中是“無限循環(huán)小數(shù)” (類似十進制的1/3=0.333...),而float和double的存儲位數(shù)有限(float占4字節(jié),double占8字節(jié)),只能“四舍五入”保留部分二進制位——這種“截斷”就導致了精度丟失,后續(xù)計算會把誤差放大,最終出現(xiàn)“0.1+0.2≠0.3”的詭異結果。
重點:只要涉及“需要精確計算”的場景(金額、財務、計量),絕對不能用float/double! 這不是“代碼寫錯了”,而是數(shù)據(jù)類型的底層特性決定的。
二、救星BigDecimal:但90%的人用錯了
很多人知道“用BigDecimal代替浮點型”,但我見過太多“用了BigDecimal還丟精度”的情況——問題出在構造方法和計算方式上,這兩個坑一定要避開。
坑1:用double構造BigDecimal(最常見錯誤)
直接用new BigDecimal(double)構造,會把double的精度誤差“帶進去”,比如:
// 錯誤用法:用double構造,精度誤差被保留
BigDecimal wrong1 = new BigDecimal(0.1);
System.out.println(wrong1); // 輸出0.1000000000000000055511151231257827021181583404541015625
// 正確用法:用String構造,完全保留十進制精度
BigDecimal right1 = new BigDecimal("0.1");
System.out.println(right1); // 輸出0.1
// 也可以用BigDecimal.valueOf(double)(底層會轉成String,推薦)
BigDecimal right2 = BigDecimal.valueOf(0.1);
System.out.println(right2); // 輸出0.1結論:構造BigDecimal時,優(yōu)先用new BigDecimal(String)或BigDecimal.valueOf(double),絕對別用new BigDecimal(double)!
坑2:用“+、-、*、/”直接計算(編譯都不通過)
BigDecimal是“對象”,不能像基本類型那樣用算術運算符計算,必須用它的成員方法(add/subtract/multiply/divide),且計算時要指定“舍入模式”(避免除不盡時拋異常)。
正確計算示例(以金額計算為例):
public class BigDecimalCalc {
public static void main(String[] args) {
// 1. 初始化金額(單位:元,用String構造)
BigDecimal price = new BigDecimal("99.9"); // 商品單價
BigDecimal quantity = new BigDecimal("3"); // 購買數(shù)量
BigDecimal discount = new BigDecimal("0.9"); // 9折優(yōu)惠
// 2. 計算:總價 = 單價 * 數(shù)量 * 折扣(用成員方法)
BigDecimal total = price.multiply(quantity).multiply(discount);
System.out.println("折后總價:" + total); // 輸出269.73
// 3. 除法示例(如拆分金額,必須指定舍入模式)
BigDecimal split = total.divide(new BigDecimal("2"), 2, BigDecimal.ROUND_HALF_UP);
System.out.println("每人分攤:" + split); // 輸出134.87(四舍五入保留2位小數(shù))
}
}
關鍵:舍入模式怎么選?(金額計算常用3種)
| 舍入模式常量 | 含義(以保留2位小數(shù)為例) | 適用場景 |
|---|---|---|
ROUND_HALF_UP | 四舍五入(1.234→1.23,1.235→1.24) | 金額計算、日常統(tǒng)計 |
ROUND_DOWN | 直接截斷(1.239→1.23) | 計算最小支付金額(不進位) |
ROUND_UP | 直接進位(1.231→1.24) | 計算稅費(不遺漏分厘) |
注意:divide方法必須指定“舍入模式”和“保留小數(shù)位數(shù)”,否則當除法結果是無限小數(shù)時(如1÷3),會拋出ArithmeticException!
三、實戰(zhàn)避坑:金額計算的3個“固定套路”
結合8年項目經(jīng)驗,總結出“金額計算(元為單位)”的標準化寫法,直接套用能避免99%的問題:
套路1:定義“保留小數(shù)位數(shù)”和“舍入模式”常量
避免硬編碼,后續(xù)修改更方便:
// 金額計算:固定保留2位小數(shù),四舍五入 private static final int SCALE = 2; private static final int ROUND_MODE = BigDecimal.ROUND_HALF_UP;
套路2:封裝“加減乘除”工具方法
重復代碼抽成工具類,減少重復錯誤:
public class MoneyUtil {
private static final int SCALE = 2;
private static final int ROUND_MODE = BigDecimal.ROUND_HALF_UP;
// 加法
public static BigDecimal add(BigDecimal a, BigDecimal b) {
return a.add(b).setScale(SCALE, ROUND_MODE);
}
// 減法
public static BigDecimal subtract(BigDecimal a, BigDecimal b) {
return a.subtract(b).setScale(SCALE, ROUND_MODE);
}
// 乘法
public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
return a.multiply(b).setScale(SCALE, ROUND_MODE);
}
// 除法
public static BigDecimal divide(BigDecimal a, BigDecimal b) {
if (BigDecimal.ZERO.compareTo(b) == 0) {
throw new IllegalArgumentException("除數(shù)不能為0");
}
return a.divide(b, SCALE, ROUND_MODE);
}
}
套路3:和數(shù)據(jù)庫交互時的“類型對應”
如果數(shù)據(jù)庫存儲金額用DECIMAL類型(推薦),Java中用BigDecimal接收,避免類型轉換丟失精度:
- 數(shù)據(jù)庫字段定義:
amount DECIMAL(10,2)(10位整數(shù)+2位小數(shù),足夠存儲千萬級金額) - MyBatis映射:直接用
java.math.BigDecimal對應,不要用double接收
四、知識擴展
在 Java 中,float 和 double 用于科學計算和工程計算,但由于它們采用二進制浮點數(shù)表示,無法精確表示某些十進制小數(shù)(如 0.1),從而導致精度丟失。
示例:
double a = 0.1; double b = 0.2; System.out.println(a + b); // 0.30000000000000004
為了進行精確的十進制運算(如金額計算),Java 提供了 BigDecimal 類。它通過用整數(shù)(BigInteger)和標度(小數(shù)點后位數(shù))的組合來精確表示任意精度的十進制數(shù)。
正確創(chuàng)建 BigDecimal 對象
錯誤方式:使用 double 構造方法
BigDecimal bd = new BigDecimal(0.1); // 0.1000000000000000055511151231257827021181583404541015625
因為 0.1 本身已經(jīng)是近似值,所以傳入 double 就失去了精度。
正確方式:使用字符串構造器
BigDecimal bd = new BigDecimal("0.1");推薦方式:使用 BigDecimal.valueOf(double)如果參數(shù)是 double 字面量或已知精確值,可使用靜態(tài)方法:
BigDecimal bd = BigDecimal.valueOf(0.1);
該方法內部先將 double 轉換為字符串再構造,能避免部分精度問題。
基本運算(加減乘除)
BigDecimal a = new BigDecimal("10.25");
BigDecimal b = new BigDecimal("3.05");
// 加法
BigDecimal sum = a.add(b); // 13.30
// 減法
BigDecimal diff = a.subtract(b); // 7.20
// 乘法
BigDecimal prod = a.multiply(b); // 31.2625
// 除法(必須指定舍入模式,否則可能拋出 ArithmeticException)
BigDecimal quot = a.divide(b, 2, RoundingMode.HALF_UP); // 3.36
// 參數(shù):除數(shù),保留小數(shù)位數(shù),舍入模式關鍵:除法必須指定精度和舍入模式,避免無限循環(huán)小數(shù)。
精度控制與舍入模式
| 舍入模式 | 說明 |
|---|---|
RoundingMode.HALF_UP | 四舍五入(最常用) |
RoundingMode.HALF_DOWN | 五舍六入 |
RoundingMode.UP | 遠離零方向舍入 |
RoundingMode.DOWN | 向零方向舍入 |
RoundingMode.CEILING | 向正無窮方向舍入 |
RoundingMode.FLOOR | 向負無窮方向舍入 |
RoundingMode.HALF_EVEN | 向偶數(shù)舍入(銀行家舍入法) |
BigDecimal d = new BigDecimal("2.335");
System.out.println(d.setScale(2, RoundingMode.HALF_UP)); // 2.34
System.out.println(d.setScale(2, RoundingMode.HALF_DOWN)); // 2.33比較 BigDecimal 對象
錯誤方式:使用 equalsequals 會比較精度(scale),例如 new BigDecimal("2.0") 與 new BigDecimal("2.00") 不相等。
正確方式:使用 compareTo
BigDecimal x = new BigDecimal("2.0");
BigDecimal y = new BigDecimal("2.00");
System.out.println(x.compareTo(y) == 0); // truecompareTo 返回 -1(小于)、0(等于)、1(大于)。
五、最后總結:3句話避開浮點精度坑
- 場景判斷:只要是“需要精確到分/厘”的計算(金額、財務),絕對不用float/double;
- 構造正確:BigDecimal用String構造或valueOf(double),別用double構造;
- 計算規(guī)范:用成員方法(add/subtract),指定舍入模式和保留位數(shù),優(yōu)先封裝工具類。
到此這篇關于Java使用BigDecimal解決精度丟失問題的文章就介紹到這了,更多相關Java BigDecimal解決精度丟失內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java圖的深度優(yōu)先遍歷實現(xiàn)隨機生成迷宮
這篇文章主要為大家詳細介紹了java圖的深度優(yōu)先遍歷實現(xiàn)隨機生成迷宮,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
Java數(shù)據(jù)結構之平衡二叉樹的實現(xiàn)詳解
平衡二叉樹又被稱為AVL樹(有別于AVL算法),且具有以下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。本文將詳解介紹一下平衡二叉樹的原理與實現(xiàn),需要的可以參考一下2022-03-03
Spring boot如何通過@Scheduled實現(xiàn)定時任務及多線程配置
這篇文章主要介紹了Spring boot如何通過@Scheduled實現(xiàn)定時任務及多線程配置,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12

