在咱们日常工作中数值计算是不可避免的,特地是电商类零碎中,这个问题个别状况下咱们都是特地留神的,然而一不注意就会出大问题,跟钱无关的事件没小事。这不新来的大兄弟就一个不留神,在这个小阴沟里翻车了,闹笑话了。
为了咱们当前能够防止在这个问题上犯错,我明天顺便写了这一篇来总结一下。
防止用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.33.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...