使用构造方法 BigDecimal(double) 的方式把 double 值转化为 BigDecimal 对象造成精度损失。
说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149
正确的示例:
// 优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals来判断。
错误的示例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
// 预期进入此代码快,执行其它业务逻辑
// 但事实上 a==b 的结果为 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 预期进入此代码快,执行其它业务逻辑
// 但事实上 equals 的结果为 false
}
正确的示例:
// 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
// 使用 BigDecimal 来定义值,再进行浮点数的运算操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
Math.abs(x)<1e-6其实相当于x == 0
1e-6(也就是0.000001)叫做epslon,用来抵消浮点运算中因为误差造成的相等无法判断的情况。它通常是一个非常小的数字(具体多小要看你的运算误差)
比如说因为精度误差,用十进制举例,我们要算1/3+1/3+1/3 == 1(从数学上说,肯定相等),但是因为精度问题,等号左边算> 出来是0.3333333+0.3333333+0.3333333 = 0.9999999,存在了误差,右边是1.0000000,那么如果直接用 == ,返回false,我们> > 希望它被视作相等。那么就要两数相减取绝对值小于epslon的办法。
BigDecimal运算
BigDecimal a = new BigDecimal(2);
BigDecimal b = new BigDecimal(3);
BigDecimal c = new BigDecimal(4);
int n = 3;
- 1、加法运算 a + b 用 add()
BigDecimal d = a.add(b);
运行结果:5
- 2、减法运算 a - b 用 subtract()
BigDecimal d = a.subtract(b);
运行结果:-1
- 3、乘法运算 a * b 用 multiply()
BigDecimal d = a.multiply(b);
运行结果:6
- 4、除法运算 a / b 用 divide()
BigDecimal d = a.divide(b);
这里运行的时候我们会发现抛出异常了:at java.math.BigDecimal.divide(Unknown Source)
是因为2/3 无穷尽,所以抛出了异常,这里我们需要确定需保留的小数长度:
BigDecimal d = a.divide(b,2,BigDecimal.ROUND_HALF_UP);
运行结果:0.67
这里的2是需保留的小数长度;BigDecimal.ROUND_HALF_UP是设定该舍入方式为四舍五入
- 5、幂运算 a ^ n 用 pow()
BigDecimal d = a.pow(n);
运行结果:8
- 6、比较大小 a < b 用 compareTo()
int e = a.compareTo(b);
运行结果:-1
如果 a < b ; 返回 -1;
如果 a = b ; 返回 0;
如果 a > b ; 返货 1;
- 8、设定精度用 setScale()
BigDecimal e = a.setScale(2,BigDecimal.ROUND_HALF_UP);
运行结果:2.00
BigDecimal的舍入模式
- 1、ROUND_UP
舍入远离零的舍入模式。
在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。
注意,此舍入模式始终不会减少计算值的大小。
- 2、ROUND_DOWN
接近零的舍入模式。
在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。
注意,此舍入模式始终不会增加计算值的大小。
- 3、ROUND_CEILING
接近正无穷大的舍入模式。
如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;
如果为负,则舍入行为与 ROUND_DOWN 相同。
注意,此舍入模式始终不会减少计算值。
- 4、ROUND_FLOOR
接近负无穷大的舍入模式。
如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;
如果为负,则舍入行为与 ROUND_UP 相同。
注意,此舍入模式始终不会增加计算值。
- 5、ROUND_HALF_UP
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。
如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。
注意,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。
- 6、ROUND_HALF_DOWN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。
如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。
- 7、ROUND_HALF_EVEN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;
如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。
注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。
此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。
如果前一位为奇数,则入位,否则舍去。
以下例子为保留小数点1位,那么这种舍入方式下的结果。
1.15>1.2 1.25>1.2
- 8、ROUND_UNNECESSARY
断言请求的操作具有精确的结果,因此不需要舍入。
如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。