菜逼直接无脑String转哈!避免精度丢失问题!
菜逼直接无脑String转哈!避免精度丢失问题!
菜逼直接无脑String转哈!避免精度丢失问题!
京东Java开发手册说:
禁止使用BigDecimal构造方法传入Double,可以使用对象.valueOf(Dubbo)
BigDecimal(double)的方式把double值转化为BigDecimal对象
• 最坏影响: 数据精度丢失文件名称:《京东JAVA代码规范-V1.0》
• 说明: BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如:BigDecimal g = new BigDecimal(0.1f);
实际的存储值为:0. 1000000000000000055511151231257827021181583404541015625
正例:优先推荐入参为String的构造方法,或使用BigDecimal的valueOf()方法(此方法内部其实执行了Double的toString,而 Double的toString按double的实际能表达的精度对尾数进行了截断)。
BigDecimal argDouble = BigDecimal.valueOf(0.1); BigDecimal argString = new BigDecimal("0.1");
BigDecimal介绍
Java在java.math包中提供的API类BigDecimal,一般情况下,对于不需要准确计算精度的数字,可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以如果需要精确计算的结果,则必须使用BigDecimal类来操作。
BigDecimal对象提供了传统的+、-、*、/等算术运算符对应的方法,通过这些方法进行相应的加减乘除操作。BigDecimal的值元素都是被Final修饰的,对象没被Final修饰。在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
实战代码
字符转BigDecimal
BigDecimal argString = new BigDecimal("0.1");
整形、浮点型转BigDecimal
BigDecimal argDouble = BigDecimal.valueOf(0.1);
保留小数、四舍五入 setScale
BigDecimal two = BigDecimal.valueOf(1.00).setScale(2);// 单独使用没问题,如果涉及计算后位数变更(如:结果为循环小数)没有取舍规则就会报错!
// BigDecimal twoError = BigDecimal.valueOf(1.875).setScale(2); // 格式位数超过,获计算结果格式位数超过目标位,则会报错java.lang.ArithmeticException: Rounding necessary
BigDecimal OK = BigDecimal.valueOf(1.000).setScale(2, RoundingMode.HALF_UP); // 指定取舍规则,计算不会报错
拓展一下,舍入模式定义在RoundingMode枚举类中,共有8种:
- RoundingMode.UP:舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。注意,此舍入模式始终不会减少计算值的大小。
- RoundingMode.DOWN:接近零的舍入模式。在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。注意,此舍入模式始终不会增加计算值的大小。
- RoundingMode.CEILING:接近正无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUNDUP 相同;如果为负,则舍入行为与 ROUNDDOWN 相同。注意,此舍入模式始终不会减少计算值。
- RoundingMode.FLOOR:接近负无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUNDDOWN 相同;如果为负,则舍入行为与 ROUNDUP 相同。注意,此舍入模式始终不会增加计算值。
- RoundingMode.HALF_UP:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。注意,这是我们在小学时学过的舍入模式(四舍五入)。
- RoundingMode.HALF_DOWN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。
- RoundingMode.HALF_EVEN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUNDHALFUP 相同;如果为偶数,则舍入行为与 ROUNDHALF_DOWN 相同。注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。如果前一位为奇数,则入位,否则舍去。以下例子为保留小数点1位,那么这种舍入方式下的结果。1.15 ==> 1.2 ,1.25 ==> 1.2
- RoundingMode.UNNECESSARY:断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
通常我们使用的四舍五入即RoundingMode.HALF_UP。
加 add
BigDecimal var = new BigDecimal("0.1");
var = var.add(new BigDecimal("1.1"));
System.out.println(var); // 1.2
减 subtract
BigDecimal var = new BigDecimal("0.1");
var = var.subtract(new BigDecimal("1.1"));
System.out.println(var); // -1.0
乘 multiply
BigDecimal var = new BigDecimal("0.1");
var = var.multiply(new BigDecimal("1.1"));
System.out.println(var); // 0.11
除 divide
BigDecimal one= BigDecimal.valueOf(1.0);
BigDecimal three = BigDecimal.valueOf(3.0);
/*
因为1/3是(0.333333)无限小数,如果不规定保留小叔,以及舍入模式,就会报错
System.out.printf("%s\n", BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3)));
*/
// 正确除法是 指定保留小数格式,以及舍入规则,如:四舍五入 BigDecimal.ROUND_HALF_UP
BigDecimal divide = one.divide(three, 2, BigDecimal.ROUND_HALF_UP); // 0.33
余数 remainder
BigDecimal ten= BigDecimal.valueOf(10.0);
BigDecimal three = BigDecimal.valueOf(3);
// 注意余数不能为0
BigDecimal remainder = ten.remainder(three); // 1.0
相等比较 只能使用compareTo结果==0, 不得使用equals
我们比较数值是否相等的时候,一般使用==,但是这个是引用对象,只能使用equals方法,但是BigDecimal的equals执行:数值大小,精度比较!所以如果单纯比较数值的话,使用compareTo方法!
BigDecimal ten = BigDecimal.valueOf(1);
BigDecimal ten2 = BigDecimal.valueOf(1.00);
System.out.println(ten == ten2); // false 地址值比较肯定false
System.out.println(ten.equals(ten2)); // false 数值比较后,比较有效精度位
System.out.println(ten.compareTo(ten2)); // 0=true -1=false
// 网上很多傻逼说比较精度,说的很不准确,1.0 与1.00 使用equals的方法按照网上描述,精度肯定不一样,但是结果返回的是true
// 原因就在于 精度说的是有效精度,这个我也是翻了源码才看到的
BigDecimal oneWith1Zero = BigDecimal.valueOf(1.0);
BigDecimal oneWith2Zero = BigDecimal.valueOf(1.00);
System.out.println(oneWith1Zero.equals(oneWith2Zero)); // true
// 进一步证实此问题
BigDecimal oneWith3Zero = BigDecimal.valueOf(1.0200);
BigDecimal oneWith8Zero = BigDecimal.valueOf(1.020000000);
System.out.println(oneWith3Zero.equals(oneWith8Zero)); // true
大小比较 compareTo结果 0表示数值相同 1表示大于 -1表示小于
BigDecimal one = BigDecimal.valueOf(1);
BigDecimal one2 = BigDecimal.valueOf(1.00);
BigDecimal two = BigDecimal.valueOf(2.00);
System.out.println(one.compareTo(one2)); // 0表示数值相同
System.out.println(one.compareTo(two)); // -1表示小于
System.out.println(two.compareTo(one)); // 1表示大于
科学计数法、工程计数法、不用科学计数法输出
BigDecimal bigDecimal = BigDecimal.valueOf(35634535255456719.22345634534124578902);
System.out.println(bigDecimal); // 默认就是toString() 3.563453525545672E+16
System.out.println(bigDecimal.toString()); // 在必要的时候使用科学计数法 3.563453525545672E+16
System.out.println(bigDecimal.toPlainString()); // 不使用任何科学计数法 35634535255456720
System.out.println(bigDecimal.toEngineeringString()); // 工程计数法 35.63453525545672E+15
货币、百分比格式输出
此Demo Copy:https://mp.weixin.qq.com/s/-tNpbIZyzmY7e6ac_6ggRg
NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CHINA); // 建立货币格式化引用,默认JRE区域货币
NumberFormat percent = NumberFormat.getPercentInstance(); // 建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位
BigDecimal loanAmount = new BigDecimal("15000.48"); // 金额
BigDecimal interestRate = new BigDecimal("0.008"); // 利率
BigDecimal interest = loanAmount.multiply(interestRate); // 相乘
System.out.println("金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest));
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤