BigDecimal类详解

BigDecimal 类详解

在 Java 编程中,处理浮点数时经常会遇到精度问题。为了解决这个问题,Java 提供了一个 BigDecimal 类,它提供了精确的浮点数运算。本文将详细介绍 BigDecimal 类的使用方法和一些常见场景。

DECIMAL(参数1, 参数2) 数据类型解析

DECIMAL(10, 2) 举例:

  • 是一种数据类型,用于存储精确的小数。
  • 总位数(Precision):第一个参数 10 表示整个数字的最大位数,包括小数点前后的所有数字。
  • 小数位数(Scale):第二个参数 2 表示小数点后的位数。

具体来说,DECIMAL(10, 2) 表示该字段可以存储最多 10 位的数字,其中小数点后有 2 位。这意味着:

  • 小数点前最多可以有 8 位数字。
  • 小数点后固定有 2 位数字。

例如,以下是一些符合 DECIMAL(10, 2) 类型的数据示例:

  • 1234567.89
  • 0.01
  • 12345.67
  • -1234.56

但以下数据不符合 DECIMAL(10, 2) 的定义:

  • 123456789.01(总位数超过 10 位)
  • 123456.7(小数点后只有 1 位)-- 例如 mysql 会默认补 0

使用 DECIMAL 类型的主要优点是它可以提供精确的小数存储和计算,这对于金融数据、货币金额等需要高精度的应用场景非常重要。与浮点数类型(如 FLOATDOUBLE)不同,DECIMAL 不会引入舍入误差。

在你的数据库设计中,DECIMAL(10, 2) 通常用于存储价格、金额等数值,确保这些值能够被精确地存储和处理。

为什么使用 BigDecimal?

在 Java 中,floatdouble 类型的变量是基于 IEEE 754 标准的浮点数表示,这可能会导致精度损失。例如,0.1 无法在二进制浮点数中精确表示,这可能会导致计算结果与预期不符。而 BigDecimal 类则提供了任意精度的浮点数运算,非常适合处理金融数据。

创建 BigDecimal 对象

BigDecimal 对象可以通过多种方式创建:

  • BigDecimal(int) 创建一个具有参数所指定整数值的对象。
  • BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 【不推荐使用】
  • BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
  • BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。【推荐使用】
    @Test
    void testConstructor() {
        System.out.println("==================构造器==================");

        // 通过 int 值构造
        BigDecimal intBig = new BigDecimal(10);
        System.out.println("intBig = " + intBig);

        // 通过 double 值构造,会有精度问题(不推荐)
        BigDecimal doubleBig = new BigDecimal(20.45);
        System.out.println("doubleBig = " + doubleBig);

        // 通过 long 值构造
        BigDecimal longBig = new BigDecimal(30L);
        System.out.println("longBig = " + longBig);

        // 通过 String 值构造(推荐)
        BigDecimal stringBig = new BigDecimal("123.45");
        System.out.println("stringBig = " + stringBig);

        // 将 double 值转换成 String 类型再构造
        Double doubleValue = new Double(20.45);
        BigDecimal doubleBig2 = new BigDecimal(doubleValue.toString());
        System.out.println("doubleBig2 = " + doubleBig2);

        // intBig = 10
        // doubleBig = 20.449999999999999289457264239899814128875732421875
        // longBig = 30
        // stringBig = 123.45
        // doubleBig2 = 20.45
    }

也可以用 BigDecimal.valueOf() 方法:

BigDecimal multiplier = BigDecimal.valueOf(100);

获取小数位数

// 获取xia
int scale = amountDecimal.scale();

基本运算

BigDecimal 提供了一系列的方法来进行基本的数学运算,包括加法、减法、乘法和除法:

    @Test
    void testAsmd() {
        System.out.println("==================加减乘除==================");

        BigDecimal value1 = new BigDecimal("100.00");
        BigDecimal value2 = new BigDecimal("12.34");

        // 加法
        BigDecimal sum = value1.add(value2); // 112.34
        System.out.println("sum = " + sum);

        // 减法
        BigDecimal difference = value1.subtract(value2); // 87.66
        System.out.println("difference = " + difference);

        // 乘法
        BigDecimal product = value1.multiply(value2); // 1234.0000
        System.out.println("product = " + product);

        // 除法(四舍五入,保留两位小数)
        BigDecimal quotient = value1.divide(value2, 2, RoundingMode.HALF_UP); // 8.10
        System.out.println("quotient = " + quotient);

        // sum = 112.34
        // difference = 87.66
        // product = 1234.0000
        // quotient = 8.10
    }

在上面的例子中,divide 方法的第二个参数是小数位数,第三个参数是舍入模式。RoundingModeBigDecimal 中的一个枚举,用于定义舍入行为。

舍入模式

  • ROUND_UP :向远离 0 的方向舍入
  • ROUND_DOWN :向零方向舍入
  • ROUND_CEILING :向正无穷方向舍入
  • ROUND_FLOOR :向负无穷方向舍入
  • ROUND_HALF_UP :向(距离)最近的一边舍入,如果两边(的距离)是相等时,向上舍入, 1.55 保留一位小数结果为 1.6,也就是我们常说的 “四舍五入
  • ROUND_HALF_DOWN :向(距离)最近的一边舍入,如果两边(的距离)是相等时,向下舍入, 例如1.55 保留一位小数结果为1.5
  • ROUND_HALF_EVEN :向(距离)最近的一边舍入,如果两边(的距离)是相等时,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
  • ROUND_UNNECESSARY :计算结果是精确的,不需要舍入模式
@Test
    void testRoundingMode() {
        System.out.println("==================舍入模式==================");
        // 注意:断言请求的操作具有精确的结果,因此【不需要舍入】。
        // 如果对获得精确结果的操作指定此舍入模式,则抛出 ArithmeticException。 -- 如果这里小数选择保留 1 位就会抛异常
        BigDecimal bigDecimal = new BigDecimal("1.55").setScale(2, RoundingMode.UNNECESSARY);
        System.out.println("Rounding mode: " + RoundingMode.UNNECESSARY + ", Value: " + bigDecimal);

        testRoundingMode(new BigDecimal("1.55"), RoundingMode.values());

        /* Rounding mode: UNNECESSARY, Value: 1.55
        Rounding mode: UP, Value: 1.6
        Rounding mode: DOWN, Value: 1.5
        Rounding mode: CEILING, Value: 1.6
        Rounding mode: FLOOR, Value: 1.5
        Rounding mode: HALF_UP, Value: 1.6
        Rounding mode: HALF_DOWN, Value: 1.5
        Rounding mode: HALF_EVEN, Value: 1.6 */
    }

    private static void testRoundingMode(BigDecimal value, RoundingMode[] modes) {
        for (RoundingMode mode : modes) {
            BigDecimal roundedValue = value.setScale(1, mode);
            System.out.printf("Rounding mode: %s, Value: %s%n", mode, roundedValue);
        }
    }

比较操作

BigDecimal 类提供了比较两个对象的方法,这些方法返回一个整数,它表示调用对象与参数对象的相对大小:

    @Test
    void testCompare() {
        System.out.println("==================比较操作==================");
        BigDecimal value = new BigDecimal("100.00");

        BigDecimal value1 = new BigDecimal("99.00");
        BigDecimal value2 = new BigDecimal("100.00");
        BigDecimal value3 = new BigDecimal("101.00");

        // 比较值
        int cmp1 = value.compareTo(value1);
        int cmp2 = value.compareTo(value2);
        int cmp3 = value.compareTo(value3);
        System.out.println("cmp1 = " + cmp1); // 1
        System.out.println("cmp2 = " + cmp2); // 0
        System.out.println("cmp3 = " + cmp3); // -1

        // 检查是否相等
        boolean isEqual1 = value.equals(value1);
        boolean isEqual2 = value.equals(value2);
        boolean isEqual3 = value.equals(value3);
        System.out.println("isEqual1 = " + isEqual1); // false
        System.out.println("isEqual2 = " + isEqual2); // true
        System.out.println("isEqual3 = " + isEqual3); // false

        // 检查是否大于
        boolean isGreaterThan1 = value.compareTo(value1) > 0;
        boolean isGreaterThan2 = value.compareTo(value2) > 0;
        boolean isGreaterThan3 = value.compareTo(value3) > 0;
        System.out.println("isGreaterThan1 = " + isGreaterThan1); // true
        System.out.println("isGreaterThan2 = " + isGreaterThan2); // false
        System.out.println("isGreaterThan3 = " + isGreaterThan3); // false
    }

格式化输出

DecimalFormat 类允许您自定义数字格式,包括小数点后的位数、分组分隔符、甚至前缀和后缀。

  • 它是 java.text 包中的一个类,用于格式化十进制数字。
    @Test
    void testFormat() {
        System.out.println("==================格式化输出==================");

        DecimalFormat df = new DecimalFormat("#,##0.00");
        double number1 = 123456.789;
        double number2 = 1233123456.783;
        String formattedNumber1 = df.format(number1); // 123,456.79
        String formattedNumber2 = df.format(number2); // 1,233,123,456.78
        System.out.println("Formatted Number1: " + formattedNumber1);
        System.out.println("Formatted Number2: " + formattedNumber2);
    }
// 这个例子展示了如何使用 DecimalFormat 对象来格式化一个 double 类型的数字,并添加了【千位分隔符】和【小数点后两位】的格式。

解析 "#,##0.00" 这个模式字符串:

  • #:表示如果存在数字则显示数字。如果位数不足,它不会填充领先符号。在分组分隔符前的 # 表示尽可能多地使用分组分隔符。
  • ,:表示分组分隔符,通常用于分隔千位、百万位等。在这个模式中,它会在每三位数字后添加一个逗号。
  • ##0:表示至少有一个有效数字(非零数字),如果数字不足,它将用零填充。## 表示如果存在数字则显示数字,如果不存在则不显示。这里的 0 表示如果数字不足,将用零填充。
  • .00:表示小数点后有两位固定位数,即使它们是零。

综上所述,"#,##0.00" 这个模式会将数字格式化为带有千位分隔符的格式,并且小数点后总是有两位小数,如果小数部分不足两位,则用零填充。

处理精度问题

在使用 BigDecimal 时,需要注意精度问题。例如,直接使用 double 构造器可能会引入初始的精度问题:

// 错误的示例,可能会有精度问题
BigDecimal bd = new BigDecimal(0.1);

正确的做法是使用字符串构造器:

// 正确的示例
BigDecimal bd = new BigDecimal("0.1");

不同写法对比

    /**
     * 不同写法对比
     * <p>
     * 浮点数(如 float 和 double)在计算机中是以二进制形式存储的。某些十进制小数无法精确地表示为二进制小数,这会导致精度丢失
     * </p>
     * 使用 float 和 double:适用于对精度要求不是特别高的情况,但要注意它们的精度限制
     * 使用 BigDecimal:适用于需要高精度计算的情况,尤其是金融计算等对精度要求很高的场景
     */
    @Test
    void test() {
        int total = 10 + 21 + 31;

        // 用 float 类型会丢失精度,不推荐
        String format1 = String.format("%.1f", (float) 10 / total * 100);
        System.out.println(format1);

        // 用 double 类型也会丢失精度,比 float 更加安全,但还是不推荐
        String format2 = String.format("%.1f", (double) 10 / total * 100);
        System.out.println(format2);

        // 推荐使用 BigDecimal
        BigDecimal numerator = new BigDecimal(10);
        BigDecimal denominator = new BigDecimal(total);
        BigDecimal oneHundred = new BigDecimal(100);
        BigDecimal percentage = numerator
                .divide(denominator, 4, RoundingMode.HALF_UP)
                .multiply(oneHundred);
        String format3 = String.format("%.1f", percentage.doubleValue());
        System.out.println(format3);
    }

结论

BigDecimal 类是 Java 中处理精确数值计算的重要工具。

  • 它提供了丰富的 API 来执行各种数学运算,并能够避免 floatdouble 类型的精度问题。
  • 在处理金融数据或需要精确计算的场景中,BigDecimal 是首选的数据类型。
  • 推荐使用 字符串 构造器来创建 BigDecimal 对象,以确保精度不受损失。

学习参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值