菜逼直接无脑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));
特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤