最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java?BigDecimal?解決浮點精度問題(用法實踐)

 更新時間:2026年02月07日 09:58:48   作者:程序員-周李斌  
本文詳細(xì)介紹了Java中BigDecimal類的原理、用法及最佳實踐,用于解決浮點數(shù)精度丟失問題,特別是在金融計算等對精度要求極高的場景中,本文將詳細(xì)講解其原理、用法及最佳實踐,感興趣的朋友跟隨小編一起看看吧

在 Java 中,floatdouble 類型因底層采用二進(jìn)制浮點數(shù)存儲,無法精確表示部分十進(jìn)制小數(shù)(如 0.1),導(dǎo)致數(shù)值計算時出現(xiàn)精度丟失問題。java.math.BigDecimal 類專為高精度十進(jìn)制運算設(shè)計,能完美解決該問題,本文將詳細(xì)講解其原理、用法及最佳實踐。

一、浮點精度問題的根源

1. 為什么float/double會丟失精度?

計算機底層以二進(jìn)制存儲浮點數(shù),而部分十進(jìn)制小數(shù)(如 0.1、0.2)轉(zhuǎn)換為二進(jìn)制時是無限循環(huán)小數(shù)。由于 float(32 位)和 double(64 位)的存儲位數(shù)有限,只能截取近似值存儲,導(dǎo)致計算時出現(xiàn)精度偏差。

代碼示例:浮點精度丟失現(xiàn)象

public class FloatPrecisionDemo {
    public static void main(String[] args) {
        System.out.println(0.1 + 0.2); // 輸出 0.30000000000000004(預(yù)期 0.3)
        System.out.println(1.0 - 0.9); // 輸出 0.09999999999999998(預(yù)期 0.1)
        System.out.println(0.1 * 3);   // 輸出 0.30000000000000004(預(yù)期 0.3)
        System.out.println(1.0 / 3);   // 輸出 0.3333333333333333(無限近似值)
    }
}

2. 精度丟失的業(yè)務(wù)影響

在金融計算(金額、稅率)、科學(xué)計算等場景中,精度丟失會導(dǎo)致嚴(yán)重問題(如金額計算錯誤、數(shù)據(jù)偏差),因此必須使用高精度計算類 BigDecimal。

二、BigDecimal 核心特性

  1. 精確存儲十進(jìn)制數(shù):底層以「整數(shù) + 標(biāo)度」(integerDigits + scale)的形式存儲,避免二進(jìn)制轉(zhuǎn)換帶來的精度損失;
  2. 支持自定義精度和舍入模式:可靈活控制計算結(jié)果的小數(shù)位數(shù)和取舍規(guī)則;
  3. 提供完整的數(shù)學(xué)運算:支持加減乘除、冪運算、比較、取整等操作;
  4. 不可變性BigDecimal 對象創(chuàng)建后無法修改,所有運算都會返回新的 BigDecimal 對象。

三、BigDecimal 基本用法

1. 正確創(chuàng)建 BigDecimal 對象

核心原則:避免使用 BigDecimal(double) 構(gòu)造方法(會繼承 double 的精度偏差),優(yōu)先使用以下兩種方式:

創(chuàng)建方式適用場景示例代碼說明
BigDecimal(String)已知精確十進(jìn)制字符串new BigDecimal("0.1")最推薦,無精度損失
BigDecimal.valueOf(double)需轉(zhuǎn)換 double 類型BigDecimal.valueOf(0.1)底層通過 Double.toString() 轉(zhuǎn)字符串,避免偏差
BigDecimal(double)不推薦(除非明確接受偏差)new BigDecimal(0.1)會存儲 0.1 的二進(jìn)制近似值,存在精度損失

代碼示例:創(chuàng)建方式對比

public class BigDecimalCreateDemo {
    public static void main(String[] args) {
        // 錯誤方式:BigDecimal(double) 存在精度損失
        BigDecimal wrong1 = new BigDecimal(0.1);
        System.out.println(wrong1); // 輸出 0.1000000000000000055511151231257827021181583404541015625
        // 正確方式1:BigDecimal(String)
        BigDecimal correct1 = new BigDecimal("0.1");
        System.out.println(correct1); // 輸出 0.1(精確)
        // 正確方式2:BigDecimal.valueOf(double)
        BigDecimal correct2 = BigDecimal.valueOf(0.1);
        System.out.println(correct2); // 輸出 0.1(精確)
    }
}

2. 核心運算方法(加減乘除)

BigDecimal 沒有重載 +、-*、/ 運算符,需通過實例方法完成運算,且除法運算必須指定舍入模式(避免除不盡時拋出異常)。

常用運算方法

運算類型方法簽名示例(a 和 b 為 BigDecimal)
加法add(BigDecimal augend)a.add(b) → 等價于 a + b
減法subtract(BigDecimal subtrahend)a.subtract(b) → 等價于 a - b
乘法multiply(BigDecimal multiplicand)a.multiply(b) → 等價于 a * b
除法divide(BigDecimal divisor, RoundingMode mode)a.divide(b, RoundingMode.HALF_UP) → 等價于 a / b(四舍五入)
除法(指定精度)divide(BigDecimal divisor, int scale, RoundingMode mode)a.divide(b, 2, RoundingMode.HALF_UP) → 保留 2 位小數(shù),四舍五入

代碼示例:精確運算

import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalCalcDemo {
    public static void main(String[] args) {
        // 1. 初始化精確數(shù)值
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");
        BigDecimal c = new BigDecimal("3");
        // 2. 加法
        BigDecimal sum = a.add(b);
        System.out.println("0.1 + 0.2 = " + sum); // 輸出 0.3(精確)
        // 3. 減法
        BigDecimal diff = new BigDecimal("1.0").subtract(new BigDecimal("0.9"));
        System.out.println("1.0 - 0.9 = " + diff); // 輸出 0.1(精確)
        // 4. 乘法
        BigDecimal product = a.multiply(c);
        System.out.println("0.1 * 3 = " + product); // 輸出 0.3(精確)
        // 5. 除法(除不盡時必須指定舍入模式)
        BigDecimal divide1 = new BigDecimal("1.0").divide(c, RoundingMode.HALF_UP);
        System.out.println("1.0 / 3 = " + divide1); // 輸出 0.33333333333333333333(默認(rèn)精度)
        // 6. 除法(指定小數(shù)位數(shù)和舍入模式)
        BigDecimal divide2 = new BigDecimal("1.0").divide(c, 2, RoundingMode.HALF_UP);
        System.out.println("1.0 / 3(保留2位) = " + divide2); // 輸出 0.33(四舍五入)
    }
}

3. 關(guān)鍵配置:舍入模式(RoundingMode)

BigDecimal 提供 RoundingMode 枚舉類定義取舍規(guī)則,常用場景(如金額計算)優(yōu)先使用 HALF_UP(四舍五入),避免使用默認(rèn)的 UNNECESSARY(除不盡時拋異常)。

常用舍入模式說明

舍入模式中文含義示例(保留 2 位小數(shù))適用場景
RoundingMode.HALF_UP四舍五入1.235 → 1.24金額、常規(guī)計算
RoundingMode.HALF_DOWN五舍六入1.235 → 1.23特定精度要求場景
RoundingMode.UP向上取整(進(jìn)一)1.231 → 1.24需高估結(jié)果(如稅費)
RoundingMode.DOWN向下取整(去尾)1.239 → 1.23需低估結(jié)果(如庫存)
RoundingMode.CEILING向正無窮取整1.23 → 1.24;-1.23 → -1.23正數(shù)向上、負(fù)數(shù)向下
RoundingMode.FLOOR向負(fù)無窮取整1.23 → 1.23;-1.23 → -1.24正數(shù)向下、負(fù)數(shù)向上
RoundingMode.UNNECESSARY無需舍入(拋異常)1.235 → 拋 ArithmeticException確保結(jié)果無余數(shù)的場景

四、BigDecimal 進(jìn)階用法

1. 精度控制與格式化

通過 setScale(int scale, RoundingMode mode) 手動設(shè)置小數(shù)位數(shù),結(jié)合 DecimalFormat 格式化輸出(如金額千分位、貨幣符號)。

代碼示例:精度控制與格式化

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
public class BigDecimalFormatDemo {
    public static void main(String[] args) {
        BigDecimal amount = new BigDecimal("12345.6789");
        // 1. 設(shè)置小數(shù)位數(shù)(保留2位,四舍五入)
        BigDecimal scaledAmount = amount.setScale(2, RoundingMode.HALF_UP);
        System.out.println("保留2位小數(shù):" + scaledAmount); // 輸出 12345.68
        // 2. 格式化輸出(千分位、貨幣符號)
        DecimalFormat df = new DecimalFormat("###,###.00"); // 保留2位小數(shù),千分位分隔
        String formatted = df.format(scaledAmount);
        System.out.println("格式化金額:" + formatted); // 輸出 12,345.68
        // 3. 格式化貨幣(如人民幣)
        DecimalFormat currencyDf = new DecimalFormat("¥###,###.00");
        System.out.println("貨幣格式:" + currencyDf.format(scaledAmount)); // 輸出 ¥12,345.68
    }
}

2. 比較大小(避免使用==)

BigDecimal 是對象,== 比較的是內(nèi)存地址,需使用 compareTo(BigDecimal val) 方法比較數(shù)值大小:

  • 返回 0:兩數(shù)相等;
  • 返回 1:當(dāng)前數(shù)大于參數(shù);
  • 返回 -1:當(dāng)前數(shù)小于參數(shù)。

代碼示例:比較大小

import java.math.BigDecimal;
public class BigDecimalCompareDemo {
    public static void main(String[] args) {
        BigDecimal x = new BigDecimal("10.00");
        BigDecimal y = new BigDecimal("10");
        BigDecimal z = new BigDecimal("10.01");
        System.out.println(x.equals(y)); // false(equals 比較值和標(biāo)度,x標(biāo)度2,y標(biāo)度0)
        System.out.println(x.compareTo(y) == 0); // true(compareTo 僅比較數(shù)值)
        System.out.println(x.compareTo(z) < 0); // true(x < z)
        System.out.println(z.compareTo(x) > 0); // true(z > x)
    }
}

3. 轉(zhuǎn)換為基本類型

通過 xxxValue() 方法將 BigDecimal 轉(zhuǎn)換為基本類型(需注意數(shù)值范圍,避免溢出):

BigDecimal num = new BigDecimal("123");
int intVal = num.intValue(); // 轉(zhuǎn)換為 int
long longVal = num.longValue(); // 轉(zhuǎn)換為 long
double doubleVal = num.doubleValue(); // 轉(zhuǎn)換為 double(大數(shù)值可能丟失精度)

五、常見坑與注意事項

1. 避免使用BigDecimal(double)構(gòu)造器

如前文所述,new BigDecimal(0.1) 會存儲 0.1 的二進(jìn)制近似值,導(dǎo)致精度丟失,必須使用字符串或 valueOf(double) 構(gòu)造。

2. 除法必須指定舍入模式

當(dāng)除法運算結(jié)果無法整除時(如 1.0 / 3),若未指定舍入模式,會拋出 ArithmeticException

// 錯誤:未指定舍入模式,拋異常
BigDecimal wrong = new BigDecimal("1.0").divide(new BigDecimal("3"));
// 正確:指定舍入模式
BigDecimal correct = new BigDecimal("1.0").divide(new BigDecimal("3"), RoundingMode.HALF_UP);

3.equals()與compareTo()的區(qū)別

  • equals():比較數(shù)值 + 標(biāo)度(如 10.0010 不相等);
  • compareTo():僅比較數(shù)值(如 10.0010 相等)。

最佳實踐:比較數(shù)值大小時用 compareTo(),判斷是否完全相等(含標(biāo)度)時用 equals()。

4. 不可變性導(dǎo)致的性能問題

BigDecimal 是不可變對象,每次運算都會創(chuàng)建新對象,頻繁運算(如循環(huán)累加)會產(chǎn)生大量臨時對象,影響性能。解決方案:

  • 循環(huán)累加時使用 MutableBigDecimal(Guava 庫提供,可變類型);
  • 非高頻場景可忽略(BigDecimal 的精度優(yōu)勢優(yōu)先于性能損耗)。

5. 空指針風(fēng)險

BigDecimal 是引用類型,可能為 null,運算前需做非空判斷:

// 推薦:非空判斷(避免空指針異常)
public static BigDecimal add(BigDecimal a, BigDecimal b) {
    a = a == null ? BigDecimal.ZERO : a;
    b = b == null ? BigDecimal.ZERO : b;
    return a.add(b);
}

六、最佳實踐總結(jié)

  1. 創(chuàng)建對象:優(yōu)先使用 BigDecimal(String)BigDecimal.valueOf(double),禁止使用 BigDecimal(double);
  2. 運算規(guī)則:除法必須指定舍入模式(推薦 RoundingMode.HALF_UP),復(fù)雜運算指定小數(shù)位數(shù);
  3. 比較大小:用 compareTo() 而非 ==equals()(除非需比較標(biāo)度);
  4. 格式化輸出:金額、數(shù)值展示時用 DecimalFormat 統(tǒng)一格式,避免直接 toString ();
  5. 非空處理:方法參數(shù)或返回值為 BigDecimal 時,需做非空判斷,默認(rèn)值用 BigDecimal.ZERO;
  6. 場景選擇:金融計算、高精度場景強制使用 BigDecimal;普通場景(無精度要求)可使用 double 提升性能。

七、典型應(yīng)用場景:金額計算

import java.math.BigDecimal;
import java.math.RoundingMode;
/**
 * 金額計算工具類(示例)
 */
public class MoneyUtils {
    // 小數(shù)位數(shù)(默認(rèn)2位,對應(yīng)分)
    private static final int SCALE = 2;
    // 舍入模式(四舍五入)
    private static final RoundingMode ROUND_MODE = RoundingMode.HALF_UP;
    // 加法
    public static BigDecimal add(BigDecimal a, BigDecimal b) {
        a = a == null ? BigDecimal.ZERO : a;
        b = b == null ? BigDecimal.ZERO : b;
        return a.add(b).setScale(SCALE, ROUND_MODE);
    }
    // 減法
    public static BigDecimal subtract(BigDecimal a, BigDecimal b) {
        a = a == null ? BigDecimal.ZERO : a;
        b = b == null ? BigDecimal.ZERO : b;
        return a.subtract(b).setScale(SCALE, ROUND_MODE);
    }
    // 乘法(如金額 × 稅率)
    public static BigDecimal multiply(BigDecimal amount, BigDecimal rate) {
        amount = amount == null ? BigDecimal.ZERO : amount;
        rate = rate == null ? BigDecimal.ZERO : rate;
        return amount.multiply(rate).setScale(SCALE, ROUND_MODE);
    }
    // 除法(如金額 ÷ 數(shù)量)
    public static BigDecimal divide(BigDecimal amount, BigDecimal count) {
        amount = amount == null ? BigDecimal.ZERO : amount;
        count = count == null ? BigDecimal.ONE : count;
        return amount.divide(count, SCALE, ROUND_MODE);
    }
    public static void main(String[] args) {
        BigDecimal price = new BigDecimal("99.99"); // 單價
        BigDecimal count = new BigDecimal("3");     // 數(shù)量
        BigDecimal rate = new BigDecimal("0.06");   // 稅率6%
        BigDecimal total = multiply(price, count); // 總價:99.99 × 3 = 299.97
        BigDecimal tax = multiply(total, rate);    // 稅費:299.97 × 0.06 = 17.9982 → 18.00
        BigDecimal finalAmount = add(total, tax);  // 最終金額:299.97 + 18.00 = 317.97
        System.out.println("總價:" + total);       // 輸出 299.97
        System.out.println("稅費:" + tax);         // 輸出 18.00
        System.out.println("最終金額:" + finalAmount); // 輸出 317.97
    }
}

通過 BigDecimal 可徹底解決浮點精度問題,尤其適用于對精度要求極高的場景。掌握其核心用法和最佳實踐,能有效避免常見錯誤,確保計算結(jié)果的準(zhǔn)確性。

到此這篇關(guān)于Java BigDecimal 解決浮點精度問題(用法實踐)的文章就介紹到這了,更多相關(guān)java bigdecimal 浮點精度內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring boot 連接多數(shù)據(jù)源過程詳解

    Spring boot 連接多數(shù)據(jù)源過程詳解

    這篇文章主要介紹了Spring boot 連接多數(shù)據(jù)源過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-08-08
  • Springboot使用JustAuth實現(xiàn)各種第三方登陸

    Springboot使用JustAuth實現(xiàn)各種第三方登陸

    本文主要介紹了Springboot使用JustAuth實現(xiàn)各種第三方登陸,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • MyBatis?枚舉映射的實現(xiàn)示例

    MyBatis?枚舉映射的實現(xiàn)示例

    本文主要介紹了MyBatis?枚舉映射的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-06-06
  • 淺談單例模式和線程安全問題

    淺談單例模式和線程安全問題

    這篇文章主要介紹了淺談單例模式和線程安全問題,再某些特殊的情況下,存在一個類僅能用來產(chǎn)生一個唯一對象的必要性,因此需要單例模式,需要的朋友可以參考下
    2023-04-04
  • SpringBoot接入deepseek深度求索示例代碼(jdk1.8)

    SpringBoot接入deepseek深度求索示例代碼(jdk1.8)

    這篇文章主要介紹了SpringBoot接入deepseek深度求索的相關(guān)資料,包括建API?key、封裝詢問Deepseek的工具方法(在配置文件中添加key值)、調(diào)用測試并確保端口一致例如8091,最后運行結(jié)果,需要的朋友可以參考下
    2025-02-02
  • SpringBoot 監(jiān)聽Redis鍵過期事件(過期監(jiān)聽)

    SpringBoot 監(jiān)聽Redis鍵過期事件(過期監(jiān)聽)

    Redis鍵過期事件是SpringBoot中常用的緩存失效通知方式,通過配置可以監(jiān)聽特定鍵的過期事件,具有一定的參考價值,感興趣的可以了解一下
    2024-12-12
  • java中ImageReader和BufferedImage獲取圖片尺寸實例

    java中ImageReader和BufferedImage獲取圖片尺寸實例

    這篇文章主要介紹了java中ImageReader和BufferedImage獲取圖片尺寸實例,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Spring Boot集成Ehcache緩存解決方式

    Spring Boot集成Ehcache緩存解決方式

    在本篇文章里小編給大家整理的是關(guān)于Spring Boot集成Ehcache緩存解決方式,需要的朋友們可以學(xué)習(xí)下。
    2019-12-12
  • Mysql字段和java實體類屬性類型匹配方式

    Mysql字段和java實體類屬性類型匹配方式

    這篇文章主要介紹了Mysql字段和java實體類屬性類型匹配方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java后臺線程操作示例【守護(hù)線程】

    Java后臺線程操作示例【守護(hù)線程】

    這篇文章主要介紹了Java后臺線程操作,結(jié)合實例形式分析了java守護(hù)線程相關(guān)原理、操作技巧與使用注意事項,需要的朋友可以參考下
    2019-09-09

最新評論

孝义市| 富阳市| 梧州市| 成安县| 东平县| 新田县| 临桂县| 泸溪县| 安丘市| 额济纳旗| 新巴尔虎左旗| 乌拉特后旗| 临颍县| 图木舒克市| 大姚县| 高清| 宜城市| 土默特右旗| 临漳县| 美姑县| 旬阳县| 商丘市| 老河口市| 开阳县| 汨罗市| 清丰县| 东山县| 张掖市| 邯郸市| 平陆县| 永宁县| 呼和浩特市| 静安区| 商水县| 迁西县| 华亭县| 四川省| 昌邑市| 平塘县| 长宁区| 孝义市|