一、BigDecimal概述

Java在java.math包中提供的API类BigDecimal,用于对超过16位有效位的数进行精确运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用FloatDouble进行处理。但是,Double.valueOf(String)Float.valueOf(String)会丢失精度。因此,在开发中,若需要精确计算结果,则必须使用BigDecimal类来操作。

BigDecimal所创建的是对象,因此我们不能使用传统的+-×÷等算术运算符直接对其对象进行数学运算,必须调用相应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,尤其是带有参数的对象。


二、理论

1、阿里手册正确规范

1.构造函数比较

构造函数比较

2.浮点数之间的等值判断

浮点数等值判断1
浮点数等值判断2


三、实操

1、Entity VS SQL

Entity VS SQL
Entity VS SQL

2、故障代码演示

好的,您希望将 m1m2m3m4 方法分开,并单独展示每个方法的内容。


1.BigDecimal 构造方法使用示范

问题背景

BigDecimal 用于高精度计算,但在构造时要小心选择合适的方法。避免使用 BigDecimal(double) 构造方法,因为 double 本身存在精度丢失问题。推荐使用 BigDecimal(String)BigDecimal.valueOf(double) 方法来避免精度丢失。

示例代码:BigDecimal 构造方法示范

private static void m1() {
    // 不推荐:会有精度丢失问题
    BigDecimal amount1 = new BigDecimal(0.03);
    BigDecimal amount2 = new BigDecimal(0.02);
    System.out.println("不推荐构造方法结果: " + amount1.subtract(amount2));  // 输出:0.010000000000000002

    // 推荐:使用字符串构造器,保证精度
    BigDecimal amount3 = new BigDecimal("0.03");
    BigDecimal amount4 = new BigDecimal("0.02");
    System.out.println("推荐构造方法结果: " + amount3.subtract(amount4));  // 输出:0.01

    // 使用BigDecimal.valueOf(double),避免精度问题
    BigDecimal amount5 = BigDecimal.valueOf(0.03);
    BigDecimal amount6 = BigDecimal.valueOf(0.02);
    System.out.println("BigDecimal.valueOf结果: " + amount5.subtract(amount6));  // 输出:0.01
}

在这里插入图片描述

解释

  • BigDecimal(0.03) 由于 0.03 是浮点数,在构造时会丢失精度。
  • 使用 BigDecimal("0.03")BigDecimal.valueOf(0.03) 可以避免精度丢失问题。

2.BigDecimal 等值比较

问题背景

BigDecimalequals() 方法会比较数值和精度。因此,0.90.90 被认为是不同的。如果只关心数值而不考虑精度,应该使用 compareTo() 方法。

示例代码:使用 compareTo()equals() 进行比较

private static void m2() {
    BigDecimal amount1 = new BigDecimal("0.9");
    BigDecimal amount2 = new BigDecimal("0.90");

    // equals() 会比较数值和精度,可能导致不一致
    System.out.println("equals比较结果: " + amount1.equals(amount2));

    // compareTo() 会比较数值大小,忽略精度
    System.out.println("compareTo比较结果: " + amount1.compareTo(amount2));
}

在这里插入图片描述

解释

  • equals() 比较时会判断精度,因此 0.90.90 被认为不相等。
  • compareTo() 比较时会忽略精度,仅比较数值大小,结果为 0 表示两个数相等。

3.BigDecimal 除法操作与精度控制

问题背景

BigDecimal 的除法操作可能会遇到无限循环小数。如果不指定精度和舍入模式,BigDecimal 会抛出异常。因此,进行除法时需要显式指定精度和舍入模式。

示例代码:BigDecimal 除法操作与精度控制

private static void m3() {
    BigDecimal amount1 = new BigDecimal("2.0");
    BigDecimal amount2 = new BigDecimal("3.0");

    // 除法时指定精度和舍入模式
    System.out.println("除法结果 (四舍五入): " + amount1.divide(amount2, 2, RoundingMode.HALF_UP));  // 输出:0.67
}

在这里插入图片描述

解释

  • BigDecimal 除法操作需要指定精度(小数点后几位)以及舍入模式(HALF_UP 表示四舍五入)。
  • 如果不指定精度,BigDecimal 会抛出异常。

4.BigDecimal 科学计数法处理

问题背景

当处理非常大的数字时,BigDecimal 会自动使用科学计数法显示。如果不希望出现科学计数法,可以使用 toPlainString() 方法转换为常规数字格式。

示例代码:处理科学计数法

private static void m4() {
    BigDecimal amount1 = BigDecimal.valueOf(1234567890123456789.3141592631415926);
    System.out.println("科学计数法: " + amount1);  // 科学计数法表示:1.23456789012345677E+18
    System.out.println("toString: " + amount1.toString());  // 科学计数法表示
    System.out.println("toPlainString: " + amount1.toPlainString());  // 使用普通数字表示

    System.out.println();

    // 使用字符串构造 BigDecimal
    BigDecimal amount2 = new BigDecimal("1234567890123456789.3141592631415926");
    System.out.println("科学计数法: " + amount2);  // 直接显示完整数字
    System.out.println("toString: " + amount2.toString());  // 直接显示完整数字
    System.out.println("toPlainString: " + amount2.toPlainString());  // 显示完整数字,不使用科学计数法
}

在这里插入图片描述

解释

  • toPlainString() 可以避免使用科学计数法,显示完整的数值。
  • toString() 和直接输出 BigDecimal 会使用科学计数法表示大数。

四、总结

  • 使用float/double时会丢失精度,而String/int不会,BigDecimal(double)存在精度损失的风险。
  • 等值比较应使用compareTo()方法,equals()方法会比较值和精度(1.0与1.00返回结果为false),而compareTo()则会忽略精度。
  • 除法的结果需要指定精度。
  • 科学计数法的使用问题

五、编码最佳实践

package com.donglin;

import java.math.BigDecimal;

public class ArithmeticUtils {

    private static final int DEF_DIV_SCALE = 10; // 默认除法运算精度

    // 提供精确的加法运算
    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    // 提供精确的加法运算(String)
    public static BigDecimal add(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2);
    }

    // 提供精确的加法运算,指定小数位
    public static String add(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    // 提供精确的减法运算
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    // 提供精确的减法运算(String)
    public static BigDecimal sub(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2);
    }

    // 提供精确的乘法运算
    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    // 提供精确的除法运算
    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    // 提供精确的小数位四舍五入处理
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    // main方法用于测试上述的运算方法
    public static void main(String[] args) {
        double num1 = 10.5;
        double num2 = 4.3;

        // 测试加法运算
        System.out.println("加法运算 (double): " + add(num1, num2));
        System.out.println("加法运算 (String): " + add("10.5", "4.3"));
        System.out.println("加法运算 (指定小数位): " + add("10.5", "4.3", 2));

        // 测试减法运算
        System.out.println("减法运算 (double): " + sub(num1, num2));
        System.out.println("减法运算 (String): " + sub("10.5", "4.3"));

        // 测试乘法运算
        System.out.println("乘法运算 (double): " + mul(num1, num2));

        // 测试除法运算
        System.out.println("除法运算 (double): " + div(num1, num2));
        System.out.println("除法运算 (double, 指定精度): " + div(num1, num2, 3));

        // 测试四舍五入
        System.out.println("四舍五入 (double): " + round(num1, 2));
    }
}



在这里插入图片描述

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐