当前位置: 首页 > 科技观察

刚来的大哥就这个小问题翻车了,你确定不看看吗?

时间:2023-03-13 16:35:21 科技观察

本文转载自微信公众号《在家乡学Java》,作者在家乡学Java。转载本文请联系家乡学Java公众号。数值计算在我们的日常工作中是不可避免的,尤其是在电子商务系统中。平时我们会特别注意这个问题,但是如果不注意,就会出大问题。跟钱有关的事情可不是小事。老大哥,不是新来的,一不留神,就把车翻在这个小阴沟里,闹出了笑话。为了避免我们以后在这个问题上犯错误,今天特地写了这篇文章来总结一下。避免使用Double计算和使用Double计算。我们认为的算术运算与计算机计算并不完全相同。这是因为计算机以二进制存储数值,我们输入的十进制数值会被转换成二进制进行计算。十进制转换二进制转十进制不是原来的十进制,也不再是以前那个男孩子了。比如:0.1十进制转二进制就是0.0001100110011...(数不清的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(newBigDecimal(0.1).add(newBigDecimal(0.2)));System.out。println(newBigDecimal(1.0).subtract(newBigDecimal(0.8)));System.out.println(newBigDecimal(4.015).multiply(newBigDecimal(100)));System.out.println(newBigDecimal(123.3)).divide(newBigDecimal)(100)));我们发现使用BigDecimal后计算结果还是不准确,这里要记住BigDecimal的第一个坑:当使用BigDecimal表示和计算浮点数时,使用String构造函数初始化BigDecimal。小改进再看结果:System.out.println(newBigDecimal("0.1").add(newBigDecimal("0.2")));System.out.println(newBigDecimal("1.0").subtract(newBigDecimal("0.8")));System.out.println(newBigDecimal("4.015").multiply(newBigDecimal("100")));System.out.println(newBigDecimal("123.3").divide(newBigDecimal("100")));那么下一个问题是,如果使用BigDecimal就万事大吉了吗?不!接下来我们看一下BigDecimal的源码。属性,scale表示小数点右边的位数,precision表示精度,也就是我们常说的有效长度。我们已经知道BigDecimal必须传入一个string类型的值,那么如果我们现在是一个Double类型的值,应该如何操作呢?让我们通过一个简单的测试来看一下:privatestaticvoidtestScale(){BigDecimalbigDecimal1=newBigDecimal(Double.toString(100));BigDecimalbigDecimal2=newBigDecimal(String.valueOf(100d));BigDecimalbigDecimal3=BigDecimal.valueOf(100d);BigDecimalbigDecimal4=newBigDecimal4(“100”);BigDecimalbigDecimal5=newBigDecimal(String.valueOf(100ig));print();print(bigDecimal2);print(bigDecimal3);print(bigDecimal4);print(bigDecimal5);}privatestaticvoidprint(BigDecimalbigDecimal){System.out.println(String.format("scale%sprecision%sresult%s",bigDecimal.scale(),bigDecimal.precision(),bigDecimal.multiply(newBigDecimal("1.001"))));}运行,我们发现上述前三种方法将double转BigDecimal后,得到的BigDecimal的标度均为1,精度为4,后两种方法的toString方法得到的标度为0,精度为3。乘以1.001后,我们发现比例是将两个数字的比例相加的结果。我们在处理浮点型字符串时,应该通过格式化表达式或格式化工具明确指定小数点后的位数和舍入方式。如何选择浮点数的舍入和格式?我们先看一下使用String.format进行格式化和四舍五入会得到什么结果,我们知道浮点数有两种,double和float,下面我们就用到这两种例如:doublenum1=3.35;floatnum2=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.3499999046327683String格式。有些是四舍五入的,所以精度问题和四舍五入的方法导致运算结果和我们预期的不一样。Formatter类默认使用HALF_UP舍入方法。如果我们需要使用其他舍入方式进行格式化,我们可以手动设置。至此,我们知道String.format格式化的方式有很多坑,所以,“浮点数的字符串格式化必须使用BigDecimal”。来,上传代码,测试一下是不是这样:BigDecimalnum1=newBigDecimal("3.35");//小数点后1位,向下取整BigDecimalnum2=num1.setScale(1,BigDecimal.ROUND_DOWN);System.out.println(num2);//小数点后1位,四舍五入BigDecimalnum3=num1.setScale(1,BigDecimal.ROUND_HALF_UP);System.out.println(num3);输入结果:3.33.4这次得到的结果是我们一致预期的。BigDecimal不能使用equals方法进行比较?我们都知道包装类的比较必须使用equals而不是==,那么BigDecimal中是否也适用呢?数据上来说,一个简单的测试来说明:System.out.println(newBigDecimal("1").equals(newBigDecimal("1.0")))Result:false按照我们的理解,1和1.0是相等的,应该是相等的,但是Bigdecimal的equals不仅在比较中比较了值,而且比较了小数位。我们说刻度是小数点后的位数。很明显,两个值的小数点后位数不同,所以结果为假。在实际使用中,我们往往只想比较两个BigDecimals的值。这里一定要注意compareTo方法的使用:System.out.println(newBigDecimal("1").compareTo(newBigDecimal("1.0")))Result:true最后总结一下今天的文章:AvoidusingDoubleto进行计算。使用String或BigDecimal.valueOf()初始化BigDecimal。建议使用BigDecimal格式化浮点数。要比较两个BigDecimals的值,使用compareTo。