在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)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
在Java中,float
和double
是原始数据类型,用于表示浮点数。它们在内存中以二进制形式存储,因此在进行浮点数运算时,可能会出现精度问题。例如,0.1 + 0.2
的结果可能不是0.3
,而是0.30000000000000004
。这是因为浮点数在二进制表示中无法精确表示某些小数。
BigDecimal
是一个不可变的、任意精度的有符号十进制数。它提供了精确的浮点数运算,避免了float
和double
的精度问题。BigDecimal
使用BigInteger
来表示无符号的定点数,并通过一个32位的整数来表示小数点的位置。
想多了解原始类型与包装类型的见文末
BigDecimal
能够解决float
和double
在以下方面的问题:
float
和double
在进行浮点数运算时,由于二进制表示的限制,可能会出现精度损失。BigDecimal
通过使用十进制表示,可以精确地表示和计算浮点数。float
和double
在进行舍入操作时,可能会出现不可预测的结果。BigDecimal
提供了多种舍入模式,可以精确控制舍入行为。float
和double
在进行比较时,由于精度问题,可能会出现不准确的结果。BigDecimal
提供了精确的比较方法,可以避免这些问题。加法
float/double:float
和double
的加法运算可能会出现精度问题。
double result = 0.1 + 0.2; // 结果可能是 0.30000000000000004
BigDecimal:BigDecimal
的加法运算可以精确表示结果。
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.add(b); // 结果是 0.3
减法
float/double:float
和double
的减法运算可能会出现精度问题。
double result = 0.3 - 0.2; // 结果可能是 0.09999999999999998
BigDecimal:BigDecimal
的减法运算可以精确表示结果。
BigDecimal a = new BigDecimal("0.3");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.subtract(b); // 结果是 0.1
乘法
float
和double
的乘法运算可能会出现精度问题。double result = 0.1 * 0.2; // 结果可能是 0.020000000000000004
BigDecimal
的乘法运算可以精确表示结果。BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.multiply(b); // 结果是 0.02
除法
float
和double
的除法运算可能会出现精度问题,尤其是当除数不能整除被除数时。double result = 1.0 / 3.0; // 结果可能是 0.3333333333333333
BigDecimal
的除法运算可以精确表示结果,但需要指定舍入模式。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/double:float
和double
在进行比较时,由于精度问题,可能会出现不准确的结果。
double a = 0.1 + 0.2;
double b = 0.3;
boolean isEqual = a == b; // 结果可能是 false
BigDecimal:
在Java中,BigDecimal
类提供了多种方法来比较两个BigDecimal
对象的大小。以下是最常用的比较方法:
compareTo(BigDecimal anotherBigDecimal)
该方法比较两个BigDecimal
对象的大小,并返回一个整数表示比较结果:
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");
}
equals(Object obj)
该方法检查当前BigDecimal
对象是否等于另一个对象。如果另一个对象也是BigDecimal
,并且两个BigDecimal
对象的数值和精度都相同,则返回true
。
BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("1.230");
boolean result = a.equals(b);
System.out.println(result); // false,因为精度不同
min(BigDecimal anotherBigDecimal)
该方法返回当前BigDecimal
对象和参数对象中的较小者。
BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("1.24");
BigDecimal min = a.min(b);
System.out.println(min); // 1.23
max(BigDecimal anotherBigDecimal)
该方法返回当前BigDecimal
对象和参数对象中的较大者。
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
。创建一个具有参数所指定以字符串表示的数值的对象
所以在使用包含小数的数据时尽量使用BigDecimal,并且用字符串来初始化它。
在初始化BigDecimal
时,建议使用String
构造函数,而不是double
构造函数。这是因为double
构造函数可能会引入精度问题。
// 错误的初始化方式
BigDecimal a = new BigDecimal(0.1); // 结果可能是 0.1000000000000000055511151231257827021181583404541015625
// 正确的初始化方式
BigDecimal a = new BigDecimal("0.1"); // 结果是 0.1
BigDecimal
提供了多种舍入模式,可以精确控制舍入行为。常见的舍入模式包括:
RoundingMode.UP
:远离零方向舍入RoundingMode.DOWN
:靠近零方向舍入RoundingMode.CEILING
:正无穷方向舍入RoundingMode.FLOOR
:负无穷方向舍入RoundingMode.HALF_UP
:四舍五入RoundingMode.HALF_DOWN
:五舍六入RoundingMode.HALF_EVEN
:银行家舍入法BigDecimal a = new BigDecimal("1.5");
BigDecimal result = a.setScale(, RoundingMode.HALF_UP); // 结果是 2
在Java中,BigDecimal
可以通过java.text.DecimalFormat
类进行格式化输出,以满足不同的显示需求,例如保留小数点后几位、使用千分位分隔符等。以下是一些常见的格式化需求及其实现方法。
DecimalFormat
格式化BigDecimal
DecimalFormat
是一个强大的工具,可以将数字格式化为指定的样式。例如,使用#,##0.00
格式可以实现以下效果:
#
和,
来表示千分位分隔符。.00
来表示保留两位小数。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);
}
}
原始数字: 1234567.8912
格式化后的数字: 1,234,567.89
格式模式:0.00
BigDecimal number =new BigDecimal("1234567.8912");
DecimalFormat formatter = new DecimalFormat("0.00");
String formattedNumber = formatter.format(number);
System.out.println(formattedNumber); // 输出:1234567.89
格式模式:#,##0.00
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
格式模式:#,##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
格式模式:#,##0.00
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
默认情况下,DecimalFormat
不会使用科学计数法。但如果需要显式避免科学计数法,可以使用toPlainString()
方法。
BigDecimal number =new BigDecimal("1234567.8912");
String plainString = number.toPlainString();
System.out.println(plainString); // 输出:1234567.8912
DecimalFormat
不会改变BigDecimal
的内部精度,只是在格式化输出时按照指定的模式显示。,
可以自动插入千分位分隔符,但需要注意不同地区的分隔符可能不同(例如,欧洲地区可能使用点.
作为千分位分隔符)。.00
;如果不需要尾随零,可以使用.##
。小数点部分末位有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考虑得太周到了真意外,然后踏了坑
获取整数部分:复制
BigDecimal a = new BigDecimal("123.456");
BigDecimal integerPart = a.setScale(, RoundingMode.DOWN); // 结果是 123
获取小数部分:
BigDecimal a = new BigDecimal("123.456");
BigDecimal fractionalPart = a.remainder(BigDecimal.ONE); // 结果是 0.456
BigDecimal
是一个强大的工具,可以解决float
和double
在精确计算中的问题。通过使用BigDecimal
,可以确保浮点数运算的精度和可靠性。在初始化BigDecimal
时,建议使用String
构造函数,以避免精度问题。BigDecimal
提供了多种舍入模式和格式化选项,可以满足不同的需求。通过这些特性,BigDecimal
在金融、科学计算等需要高精度的领域中得到了广泛的应用。
int
的默认值是0
,boolean
的默认值是false
)。存储:存储在堆内存中,是对象。
默认值:默认值为null
。
功能:提供了更多的方法和功能,例如Integer.parseInt()
、Character.isLetter()
等。
自动装箱与拆箱:Java 5引入了自动装箱(Autoboxing)和自动拆箱(Unboxing),允许在原始类型和包装类之间自动转换。 复制
Integer x = ; // 自动装箱
int y = x; // 自动拆箱
使用场景
注意事项
空值问题:包装类可以为null
,而原始类型不能为null
。这可能导致空指针异常。复制
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很简洁很实用,但有坑!
性能问题:虽然自动装箱和拆箱很方便,但过多使用可能会导致性能下降,尤其是在高并发或大量数据处理时。
默认值问题:原始类型的默认值是固定的(如int
为0
),而包装类的默认值是null
。
当后端通过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