掌握Java BigDecimal:精确计算的技巧与常见问题
目录
一、BigDecimal概述
Java在java.math包中提供的API类BigDecimal,用于对超过16位有效位的数进行精确运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double进行处理。但是,Double.valueOf(String)和Float.valueOf(String)会丢失精度。因此,在开发中,若需要精确计算结果,则必须使用BigDecimal类来操作。
BigDecimal所创建的是对象,因此我们不能使用传统的+、-、×、÷等算术运算符直接对其对象进行数学运算,必须调用相应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,尤其是带有参数的对象。
二、理论
1、阿里手册正确规范
1.构造函数比较

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


三、实操
1、Entity VS SQL


2、故障代码演示
好的,您希望将 m1、m2、m3、m4 方法分开,并单独展示每个方法的内容。
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 等值比较
问题背景
BigDecimal 的 equals() 方法会比较数值和精度。因此,0.9 和 0.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.9和0.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));
}
}

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

所有评论(0)