关于后端:刚来的大兄弟在这个小问题上翻车了你确定不看一下吗

34次阅读

共计 3872 个字符,预计需要花费 10 分钟才能阅读完成。

在咱们日常工作中数值计算是不可避免的,特地是电商类零碎中,这个问题个别状况下咱们都是特地留神的,然而一不注意就会出大问题,跟钱无关的事件没小事。这不新来的大兄弟就一个不留神,在这个小阴沟里翻车了,闹笑话了。

为了咱们当前能够防止在这个问题上犯错,我明天顺便写了这一篇来总结一下。

防止用 Double 来进行运算

应用 Double 来计算,咱们认为的算术运算和计算机计算的并不齐全始终,这是因为计算机是以二进制存储数值的,咱们输出的十进制数值都会转换成二进制进行计算,十进制转二进制再转换成十进制就不是原来那个十进制了,再也不是已经那个少年了。举个例子:十进制的 0.1 转换成二进制是 0.0 0011 0011 0011…(无数个 0011),再转换成十进制就是 0.1000000000000000055511151231,看到了吧,没有骗你的。

计算机无法准确地表白浮点数,这是不可避免的,这是为什么浮点数计算后精度损失的起因。

System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);
复制代码 

通过简略的例子,咱们发现精度损失并不是很大,然而这并不代表咱们能够应用,特地是电商类零碎中,每天少说几百万的单量,每笔订单哪怕少计算一分钱,算下来也是一笔不小的金额,所以说,这不是个小事件,而后很多人就说,金额计算啊,你用 BigDecimal 啊,对的,这个没故障,然而用了 BigDecimal 就完事大吉了吗?当问出这句话的时候,就阐明这其中必有蹊跷。

BigDecimal 你遇见过哪些坑?

还是通过一个简略的例子,计算上边例子中的运算,来看一下后果:

System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));
复制代码 

咱们发现应用了 BigDecimal 之后计算结果还是不准确,这里就要记住 BigDecimal 的第一个坑了:

BigDecimal 来示意和计算浮点数的时候,要应用 String 的构造方法来初始化 BigDecimal。

小的改良一下再来看看后果:

System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));
复制代码 

那么接下来一个问题,应用了 BigDecimal 就高枕无忧了吗?并不是的!

《2020 最新 Java 根底精讲视频教程和学习路线!》

接下来咱们来看一下 BigDecimal 的源码,这外面有一个中央须要留神,先看图:

留神看这两个属性,scale 示意小数点左边几位,precision 示意精度,就是咱们常说的无效长度。

前边咱们曾经晓得,BigDecimal 必须传入字符串类型数值,那么如果咱们当初是一个 Double 类型数值,该如何操作呢?通过一个简略的测试咱们来看一下:

 private static void testScale() {BigDecimal bigDecimal1 = new BigDecimal(Double.toString(100));
     BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(100d));
     BigDecimal bigDecimal3 = BigDecimal.valueOf(100d);
     BigDecimal bigDecimal4 = new BigDecimal("100");
     BigDecimal bigDecimal5 = new BigDecimal(String.valueOf(100));

     print(bigDecimal1);
     print(bigDecimal2);
     print(bigDecimal3);
     print(bigDecimal4);
     print(bigDecimal5);     
}

private static void print(BigDecimal bigDecimal) {System.out.println(String.format("scale %s precision %s result %s", bigDecimal.scale(), bigDecimal.precision(), bigDecimal.multiply(new BigDecimal("1.001"))));
}
复制代码 

run 一下咱们发现,以上前三种形式是将 double 转换成 BigDecimal 之后,失去的 BigDecimal 的 scale 都是 1,precision 都是 4,后两种形式的 toString 办法失去的 scale 都是 0,precision 都是 3,与 1.001 进行乘运算后,咱们发现,scale 是两个数的 scale 相加的后果。

咱们在解决浮点数的字符串的时候,应该显式的形式通过格式化表达式或者格式化工具来明确小数位数和舍入形式。

浮点数的舍入和格式化该如何抉择?

咱们首先来看看应用 String.format 的格式化舍入,会有什么后果,咱们晓得浮点数有 double 和 float 两种,下边咱们就用这两种来举例子:

double num1 = 3.35;
float num2 = 3.35f;
System.out.println(String.format("%.1f", num1));
System.out.println(String.format("%.1f", num2));
复制代码 

失去的后果仿佛与咱们的预期有出入,其实这个问题也很好解释,double 和 float 的精度是不同的,double 的 3.35 相当于 3.350000000000000088817841970012523233890533447265625,而 float 的 3.35 相当于 3.349999904632568359375,String.format 才有的又是四舍五入的形式舍入,所以精度问题和舍入形式就导致了运算后果与咱们预期不同。

Formatter 类中默认应用的是 HALF_UP 的舍入形式,如果咱们须要应用其余的舍入形式来格式化,能够手动设置。

到这里咱们就晓得通过 String.format 的形式来格式化这条路坑有点多,所以, 浮点数的字符串格式化还得要应用 BigDecimal 来进行

来,上代码,测试一下到底是不是那么回事:

BigDecimal num1 = new BigDecimal("3.35");
// 小数点后 1 位,向下舍入
BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN);
System.out.println(num2);
// 小数点后 1 位,四舍五入
BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP);
System.out.println(num3);
输出后果:3.3
3.4
复制代码 

这次失去的后果与咱们预期统一。

BigDecimal 不能应用 equals 办法比拟?

咱们都晓得,包装类的比拟要应用 equals,而不能应用 ==,那么这一条在 Bigdecimal 中也实用吗?数据谈话,简略的一个测试来阐明:

System.out.println(new BigDecimal("1").equals(new BigDecimal("1.0")))
后果:false
复制代码 

依照咱们的了解 1 和 1.0 是相等的,也应该是相等的,然而 Bigdecimal 的 equals 在比拟中不只是比拟了 value,还比拟了 scale,咱们前边说了 scale 是小数点后的位数,显著两个值的小数点后位数不一样,所以后果位 false。

理论的应用中,咱们经常是只心愿比拟两个 BigDecimal 的 value,这里就要留神,要应用 compareTo 办法:

System.out.println(new BigDecimal("1").compareTo(new BigDecimal("1.0")))
后果:true
复制代码 

最初

再总结一下明天的文章:

  • 防止应用 Double 来进行运算
  • BigDecimal 的初始化要应用 String 入参或者 BigDecimal.valueOf()
  • 浮点数的格式化倡议应用 BigDecimal
  • 比拟两个 BigDecimal 的 value 要应用 compareTo

链接:https://juejin.cn/post/690673…

正文完
 0