前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >没错,这是全网最全的BigDecimal最佳实践,不接收反驳

没错,这是全网最全的BigDecimal最佳实践,不接收反驳

作者头像
烟雨平生
发布2025-02-05 15:51:05
发布2025-02-05 15:51:05
19100
代码可运行
举报
文章被收录于专栏:数字化之路数字化之路
运行总次数:0
代码可运行

在Java编程中,使用浮点类型如double和float时经常会遇到精度问题,因为这些基本类型是以二进制形式来表示小数的,这就可能导致一些简单的算术运算无法被精确表示。例如,0.1 + 0.2并不是等于0.3,而是一个接近0.3的数。这里,BigDecimal就显得非常重要,它能够提供准确的小数运算。

那么,BigDecimal为什么可以在表示小数时保证精度不丢失?

BigDecimal在计算时,实际会把数值扩大10的n次倍,变成一个long型整数进行计算,整数计算时自然可以实现精度不丢失。同时结合精度scale,就可以实现预期的最终结果的计算。 公众号:Java基基BigDecimal 为什么可以保证精度不丢失?

虽然BigDecimal提供了精确的计算,但其运算速度比double或float都慢,应仅在确实需要高精度计算的场景下使用BigDecimal。

在日常开发中,BigDecimal类被广泛用于精确的数值、金额的计算。但是在使用BigDecimal的过程中,存在以下这几个坑,大家要注意一下哈~~

最佳实践

1、BigDecimal初始化小数时,一定要用字符串形式。

使用BigDecimal时,一个常见的陷阱是直接使用double构造函数。这种方式可能导致精度不准确,因为已经失去的精度无法通过BigDecimal恢复。解决方案:推荐使用字符串构造函数。例如,new BigDecimal("0.1")比new BigDecimal(0.1)更能保证数值的精度。

2、BigDecimal类型变量比较大小时用compareTo方法,判断变量值是否为0,与BigDecimal.ZERO比较大小。

3、BigDecimal作除法时,除了要考虑除数是否为0,更要考虑是否能除尽的问题,直接调用BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)方法做除法可以避免除不尽的问题。

一条经验都是无数人踩坑后的血泪总结。

所以,在涉及到精度计算的过程中,我们尽量使用String类型来进行转换。 正确用法如下: BigDecimal bigDecimal2=new BigDecimal("8.8"); BigDecimal bigDecimal3=new BigDecimal("8.812"); System.out.println( bigDecimal2.compareTo(bigDecimal3)); System.out.println( bigDecimal2.add(bigDecimal3)); 树洞君,公众号:石杉的架构笔记BigDecimal使用不当,造成P0事故!

下面是一些不常用,就是会时不时用到,需要的同学可以随手翻一翻,不需要全部学会,这些内容在实际中使用的时候再翻阅都来得及。

概览

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。

一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。

在需要精确的小数计算时再使用BigDecimal,BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。

尽量使用参数类型为String的构造函数。BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。

一些鸡零狗碎的小点

1. BigDecimal与Float、Double这种有原始类型的区别

在Java中,floatdouble是原始数据类型,用于表示浮点数。它们在内存中以二进制形式存储,因此在进行浮点数运算时,可能会出现精度问题。例如,0.1 + 0.2的结果可能不是0.3,而是0.30000000000000004。这是因为浮点数在二进制表示中无法精确表示某些小数。

BigDecimal是一个不可变的、任意精度的有符号十进制数。它提供了精确的浮点数运算,避免了floatdouble的精度问题。BigDecimal使用BigInteger来表示无符号的定点数,并通过一个32位的整数来表示小数点的位置。

想多了解原始类型与包装类型的见文末

2. BigDecimal能解决哪些float、double无法解决的问题?

BigDecimal能够解决floatdouble在以下方面的问题:

  • 精度问题floatdouble在进行浮点数运算时,由于二进制表示的限制,可能会出现精度损失。BigDecimal通过使用十进制表示,可以精确地表示和计算浮点数。
  • 舍入问题floatdouble在进行舍入操作时,可能会出现不可预测的结果。BigDecimal提供了多种舍入模式,可以精确控制舍入行为。
  • 比较问题floatdouble在进行比较时,由于精度问题,可能会出现不准确的结果。BigDecimal提供了精确的比较方法,可以避免这些问题。
3. BigDecimal的加、减、乘、除与float、double的异同
BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法【add、subtract、multiply、divide】。方法中的参数也必须是BigDecimal的对象。
float、double可以使用传统的+、-、*、/等算术运算符直接进行运算。

加法

float/doublefloatdouble的加法运算可能会出现精度问题。

代码语言:javascript
代码运行次数:0
复制
double result = 0.1 + 0.2; // 结果可能是 0.30000000000000004

BigDecimalBigDecimal的加法运算可以精确表示结果。

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.add(b); // 结果是 0.3

减法

float/doublefloatdouble的减法运算可能会出现精度问题。

代码语言:javascript
代码运行次数:0
复制
double result = 0.3 - 0.2; // 结果可能是 0.09999999999999998

BigDecimalBigDecimal的减法运算可以精确表示结果。

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("0.3");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.subtract(b); // 结果是 0.1

乘法

  • float/doublefloatdouble的乘法运算可能会出现精度问题。
代码语言:javascript
代码运行次数:0
复制
double result = 0.1 * 0.2; // 结果可能是 0.020000000000000004
  • BigDecimalBigDecimal的乘法运算可以精确表示结果。
代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.multiply(b); // 结果是 0.02

除法

  • float/doublefloatdouble的除法运算可能会出现精度问题,尤其是当除数不能整除被除数时。
代码语言:javascript
代码运行次数:0
复制
double result = 1.0 / 3.0; // 结果可能是 0.3333333333333333
  • BigDecimalBigDecimal的除法运算可以精确表示结果,但需要指定舍入模式。
代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
BigDecimal result = a.divide(b, , RoundingMode.HALF_UP); // 结果是 0.3333333333

4. BigDecimal比较大小与float、double的异同

float/doublefloatdouble在进行比较时,由于精度问题,可能会出现不准确的结果。

代码语言:javascript
代码运行次数:0
复制
double a = 0.1 + 0.2;
double b = 0.3;
boolean isEqual = a == b; // 结果可能是 false

BigDecimal

在Java中,BigDecimal类提供了多种方法来比较两个BigDecimal对象的大小。以下是最常用的比较方法:

4.1. compareTo(BigDecimal anotherBigDecimal)

该方法比较两个BigDecimal对象的大小,并返回一个整数表示比较结果:

  • 如果当前对象大于参数对象,返回正整数。
  • 如果当前对象等于参数对象,返回0。
  • 如果当前对象小于参数对象,返回负整数。
代码语言:javascript
代码运行次数:0
复制
BigDecimal a =newBigDecimal("1.23");
BigDecimal b =newBigDecimal("1.24");

int result = a.compareTo(b);
if(result >){
    System.out.println("a 大于 b");
}elseif(result ==){
    System.out.println("a 等于 b");
}else{
    System.out.println("a 小于 b");
}

4.2. equals(Object obj)

该方法检查当前BigDecimal对象是否等于另一个对象。如果另一个对象也是BigDecimal,并且两个BigDecimal对象的数值和精度都相同,则返回true

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("1.230");

boolean result = a.equals(b);
System.out.println(result); // false,因为精度不同

4.3. min(BigDecimal anotherBigDecimal)

该方法返回当前BigDecimal对象和参数对象中的较小者。

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("1.24");

BigDecimal min = a.min(b);
System.out.println(min); // 1.23

4.4. max(BigDecimal anotherBigDecimal)

该方法返回当前BigDecimal对象和参数对象中的较大者。

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("1.24");

BigDecimal max = a.max(b);
System.out.println(max); // 1.24

注意事项

  • compareTo方法比较的是数值大小,而equals方法比较的是数值和精度是否都相同
  • 在进行比较时,如果两个BigDecimal对象的精度不同,compareTo方法会根据数值大小进行比较,而equals方法会返回false
5. 其它不同
5.1 BigDecimal初始化时的注意事项,要使用String这个构造函数
  • BigDecimal(String)

创建一个具有参数所指定以字符串表示的数值的对象

所以在使用包含小数的数据时尽量使用BigDecimal,并且用字符串来初始化它。

在初始化BigDecimal时,建议使用String构造函数,而不是double构造函数。这是因为double构造函数可能会引入精度问题。

代码语言:javascript
代码运行次数:0
复制
// 错误的初始化方式
BigDecimal a = new BigDecimal(0.1); // 结果可能是 0.1000000000000000055511151231257827021181583404541015625

// 正确的初始化方式
BigDecimal a = new BigDecimal("0.1"); // 结果是 0.1
5.2 BigDecimal四舍五入时的几种情况

BigDecimal提供了多种舍入模式,可以精确控制舍入行为。常见的舍入模式包括:

  • RoundingMode.UP:远离零方向舍入
  • RoundingMode.DOWN:靠近零方向舍入
  • RoundingMode.CEILING:正无穷方向舍入
  • RoundingMode.FLOOR:负无穷方向舍入
  • RoundingMode.HALF_UP:四舍五入
  • RoundingMode.HALF_DOWN:五舍六入
  • RoundingMode.HALF_EVEN:银行家舍入法
代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("1.5");
BigDecimal result = a.setScale(, RoundingMode.HALF_UP); // 结果是 2
5.3 BigDecimal格式化

在Java中,BigDecimal可以通过java.text.DecimalFormat类进行格式化输出,以满足不同的显示需求,例如保留小数点后几位、使用千分位分隔符等。以下是一些常见的格式化需求及其实现方法。

5.3.1. 使用DecimalFormat格式化BigDecimal

DecimalFormat是一个强大的工具,可以将数字格式化为指定的样式。例如,使用#,##0.00格式可以实现以下效果:

  • 千分位分隔符:使用#,来表示千分位分隔符。
  • 小数点后两位:使用.00来表示保留两位小数
示例代码
代码语言:javascript
代码运行次数:0
复制
import java.math.BigDecimal;
import java.text.DecimalFormat;

public class BigDecimalFormatting{
    public static void main(String[] args){
        BigDecimal number =new BigDecimal("1234567.8912");
        // 创建DecimalFormat对象,指定格式
      DecimalFormat formatter =new DecimalFormat("#,##0.00");
        // 格式化BigDecimal
        String formattedNumber = formatter.format(number);
        System.out.println("原始数字: "+ number);
        System.out.println("格式化后的数字: "+ formattedNumber);
    }
}
输出结果
代码语言:javascript
代码运行次数:0
复制
原始数字: 1234567.8912
格式化后的数字: 1,234,567.89

2. 常见格式化模式

2.1 保留两位小数,不使用千分位分隔符

格式模式:0.00

代码语言:javascript
代码运行次数:0
复制
BigDecimal number =new BigDecimal("1234567.8912");
DecimalFormat formatter = new DecimalFormat("0.00");
String formattedNumber = formatter.format(number);
System.out.println(formattedNumber); // 输出:1234567.89
2.2 保留两位小数,使用千分位分隔符

格式模式:#,##0.00

代码语言:javascript
代码运行次数:0
复制
BigDecimal number =new BigDecimal("1234567.8912");
DecimalFormat formatter = new DecimalFormat("#,##0.00");
String formattedNumber = formatter.format(number);
System.out.println(formattedNumber); // 输出:1,234,567.89
2.3 保留两位小数,不显示小数点后的尾随零

格式模式:#,##0.##

代码语言:javascript
代码运行次数:0
复制
BigDecimal number =new BigDecimal("1234567.1012");
DecimalFormat formatter = new DecimalFormat("#,##0.##");
String formattedNumber = formatter.format(number);
System.out.println(formattedNumber); // 输出:1,234,567.1
2.4 保留两位小数,显示小数点后的尾随零

格式模式:#,##0.00

代码语言:javascript
代码运行次数:0
复制
BigDecimal number =new BigDecimal("1234567.1012");
DecimalFormat formatter = new DecimalFormat("#,##0.00");
String formattedNumber = formatter.format(number);
System.out.println(formattedNumber); // 输出:1,234,567.10
2.5 不使用科学计数法

默认情况下,DecimalFormat不会使用科学计数法。但如果需要显式避免科学计数法,可以使用toPlainString()方法。

代码语言:javascript
代码运行次数:0
复制
BigDecimal number =new BigDecimal("1234567.8912");
String plainString = number.toPlainString();
System.out.println(plainString); // 输出:1234567.8912

3. 注意事项

  • 精度问题DecimalFormat不会改变BigDecimal的内部精度,只是在格式化输出时按照指定的模式显示
  • 千分位分隔符:使用,可以自动插入千分位分隔符,但需要注意不同地区的分隔符可能不同(例如,欧洲地区可能使用点.作为千分位分隔符)。
  • 小数点后尾随零:如果需要保留尾随零,格式模式中应使用.00;如果不需要尾随零,可以使用.##

小数点部分末位有0时如何处理

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("1.2300");
String result = a.stripTrailingZeros().toPlainString(); // 结果是 1.23

关于千位分隔符: “#,##0.00”是一个数字格式的表达方式,通常用于财务和表格软件中,表示数字应该以千位分隔符(逗号)和两位小数点(两位数字)来显示。例如: 数字1234567.891会显示为“1,234,567.89”。 数字12345.678会显示为“12,345.68”。 这种格式有助于清晰地展示数值,尤其是在处理大量数据时,可以快速识别数值的大小和精确度。 唐成,公众号:的数字化之路EasyExcel考虑得太周到了真意外,然后踏了坑

5.4 如何获取BigDecimal的小数部分和整数部分

获取整数部分:复制

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("123.456");
BigDecimal integerPart = a.setScale(, RoundingMode.DOWN); // 结果是 123

获取小数部分

代码语言:javascript
代码运行次数:0
复制
BigDecimal a = new BigDecimal("123.456");
BigDecimal fractionalPart = a.remainder(BigDecimal.ONE); // 结果是 0.456

小结

BigDecimal是一个强大的工具,可以解决floatdouble在精确计算中的问题。通过使用BigDecimal,可以确保浮点数运算的精度和可靠性。在初始化BigDecimal时,建议使用String构造函数,以避免精度问题。BigDecimal提供了多种舍入模式和格式化选项,可以满足不同的需求。通过这些特性,BigDecimal在金融、科学计算等需要高精度的领域中得到了广泛的应用。

原始类型与对应的包装类

特点对比

1. 原始类型(Primitive Types)
  • 存储:直接存储值,存储在栈内存中。
  • 默认值:有默认值(例如,int的默认值是0boolean的默认值是false)。
  • 性能:通常比包装类更快,因为它们直接存储值,不需要对象的开销。
  • 用途:适用于需要高性能和简单数值操作的场景。
2. 包装类(Wrapper Classes)

存储:存储在堆内存中,是对象。

默认值:默认值为null

功能:提供了更多的方法和功能,例如Integer.parseInt()Character.isLetter()等。

自动装箱与拆箱:Java 5引入了自动装箱(Autoboxing)和自动拆箱(Unboxing),允许在原始类型和包装类之间自动转换。 复制

代码语言:javascript
代码运行次数:0
复制
Integer x = ; // 自动装箱
int y = x;      // 自动拆箱
  • 用途:适用于需要使用对象方法或泛型的场景。

使用场景

  • 原始类型:在性能敏感的场景中(如循环、数组操作)推荐使用原始类型。
  • 包装类:在需要使用对象特性(如泛型、集合框架)或调用方法时,使用包装类。

注意事项

空值问题:包装类可以为null,而原始类型不能为null。这可能导致空指针异常。复制

代码语言:javascript
代码运行次数:0
复制
Integer x = null;
int y = x; // 会报NullPointerException

//条件操作符?:返回的数据类型是int,这是一个基本数据类型 int invoiceInfoId=Objects.isNull(user)?0:user.getUserInvoiceId(); 果然是你啊,条件操作符if-else,你会把对象转成基本数据类型啊,如果user.getUserInvoiceId是null则会在unbox时报NPE: java.lang.NullPointerException:cannot unbox null value 原因:Java编译器会遵循类型一致性原则,确保三元操作符的两个分支具有相同或兼容的类型。 解决办法见原文,因字数了限制贴不下了 唐成,公众号:的数字化之路Java的条件操作符if-else很简洁很实用,但有坑!

性能问题:虽然自动装箱和拆箱很方便,但过多使用可能会导致性能下降,尤其是在高并发或大量数据处理时。

默认值问题:原始类型的默认值是固定的(如int0),而包装类的默认值是null

REFERENCE

当后端通过RESTful接口发送Long类型数据给前端时,如果这个数据超出了JavaScript的安全整数范围,前端接收到的数据可能会有精度丢失。 因为,在JavaScript中,所有数字都是以64位浮点数的形式存储的,即使整数也是如此。 路条编程,公众号:路条编程Spring Boot 2.x 解决接口中 Long 类型数据在前端精度丢失问题

因为js数字类型最大长度为16位,而java的long类型的最大长度为19位。所以如果数据长度大于16位的话传输到前端就会丢失精度。 springboot葵花宝典,公众号:springboot葵花宝典解密Long型数据传递:Spring Boot后台如何避免精度丢失问题

BigDecimal和long都不包含单位/币种这些基本信息,故都不好。相比之下,BigDecimal更糟糕。基本的跨平台能力都没有,BigDecimal在特定芯片环境下,可能因芯片不同,出现与实际运算不同的答案,用Long其它更稳妥一点,但就是表达小数需要转换。 BigDecimal小数点/有效位这些潜规则隐藏很深,甚至数字极大的时候都可能牺牲精度,牺牲了精度你还不知道,因为没有报错。 好的设计应“有限且明确”,而不是隐藏含糊。 程序不是能跑起来、不出错就行了,要考虑设计能不能自然体现业务需求,好不好理解、扩展和维护。 公众号:码问争论不休:金额用Long还是BigDecimal?

1)参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入new BigDecimal(0.1)所创建的BigDecimal正好等于0.1(非标度值1,其标度为1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于0.1(虽然表面上等于该值)。 公众号:Java基基公司新来一个同事,把 BigDecimal 运用的炉火纯青!

BigDecimal是Java中处理精确计算不可或缺的工具,尤其是在需要高度精确的数学运算时。 虽然BigDecimal提供了精确的计算,但其运算速度比double或float慢。解决方案:应仅在确实需要高精度计算的场景下使用BigDecimal。 Apache Commons库中的ArithmeticUtils提供了一系列用于精确计算的静态方法,是处理高精度运算时的好帮手。 爱他就关注他--->,公众号:Java分享客栈BigDecimal的雷区,千万不要踩!

10除以3,结果是无限循环小数3.3333...,未指定精度和舍入模式会抛出ArithmeticException。 官方的解释:"If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations." 捡田螺的小男孩,公众号:捡田螺的小男孩又踩坑了!BigDecimal使用的5个坑!

BigDecimal 由任意精度的整数非标度值和32位的整数标度(scale)组成。 如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以10的负scale次幂。 因此,BigDecimal表示的数值是(unscaledValue×10-scale)。 与之相关的还有两个类: java.math.MathContext: 该对象是封装上下文设置的不可变对象,它描述数字运算符的某些规则,如数据的精度,舍入方式等。 java.math.RoundingMode: 这是一种枚举类型,定义了很多常用的数据舍入方式。 这个类用起来还是很比较复杂的,原因在于舍入模式, https://www.cnblogs.com/softidea/p/6912513.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 最佳实践
  • 概览
  • 一些鸡零狗碎的小点
    • 1. BigDecimal与Float、Double这种有原始类型的区别
    • 2. BigDecimal能解决哪些float、double无法解决的问题?
    • 3. BigDecimal的加、减、乘、除与float、double的异同
    • BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法【add、subtract、multiply、divide】。方法中的参数也必须是BigDecimal的对象。
    • float、double可以使用传统的+、-、*、/等算术运算符直接进行运算。
    • 4.1. compareTo(BigDecimal anotherBigDecimal)
    • 4.2. equals(Object obj)
    • 4.3. min(BigDecimal anotherBigDecimal)
    • 4.4. max(BigDecimal anotherBigDecimal)
    • 注意事项
      • 5. 其它不同
    • 5.3.1. 使用DecimalFormat格式化BigDecimal
      • 示例代码
      • 输出结果
    • 2. 常见格式化模式
      • 2.1 保留两位小数,不使用千分位分隔符
      • 2.2 保留两位小数,使用千分位分隔符
      • 2.3 保留两位小数,不显示小数点后的尾随零
      • 2.4 保留两位小数,显示小数点后的尾随零
      • 2.5 不使用科学计数法
    • 3. 注意事项
  • 小结
    • 原始类型与对应的包装类
    • 特点对比
      • 1. 原始类型(Primitive Types)
      • 2. 包装类(Wrapper Classes)
  • REFERENCE
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档