double计算分析

来源:互联网 发布:心书网络 更新朋友圈 编辑:程序博客网 时间:2024/05/29 19:56

关于订单号的设计前几篇文章中已经做了一些介绍,相信大家对订单号生成的原则和方法有一定的认识了。接下来就介绍另一个比较常用的设计,也是初学者很容易踏进去的坑——金额数据的存储。

根据习惯,小编也是,以前的开发过程中金额很自然的就存储成了double类型。以“元”为单位,“分”就是小数点后两位。直白明了,便于使用,便于理解。

然而,这其中也存在一些坑。与大家分享一下,先看代码:

public class Test{    public static void main(String args[]){        System.out.println(0.05+0.01);        System.out.println(1.0-0.42);        System.out.println(4.015*100);        System.out.println(123.3/100);    }};

接下来,看一下结果:

0.0600000000000000050.5800000000000001401.499999999999941.2329999999999999

是的,你没看错。

Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。现在终于理解为什么要有BCD码了。

这个问题相当严重,如果你有9.999999999999元,你的计算机是不会认为你可以购买10元的商品的。

在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。现在让我们看看如何解决这个问题。

1、四舍五入

这也是一个正常人的第一反应,毕竟从小学就学了这个方法。但是Math的round方法不能设置保留几位小数。所以我们只能先转换一步,然后round计算,然后再除以100。代码如下:

public double round(double value){    return Math.round(value*100)/100.0;}

然而,计算机不是人。

上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02。

4.015*100=401.49999999999994

因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算。

java.text.DecimalFormat也不能解决这个问题: System.out.println(new java.text.DecimalFormat("0.00").format(4.015));

从代码上也可以看的出,输出的结果是:

4.01

那该怎么解决呢?接下来看方案2.

2、BigDecimal

借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

然而,这里面也是存在一个让人容易掉进去的坑。废话少说,上代码:

public class Test{    public static void main(String args[]){        BigDecimal aDouble =new BigDecimal(1.22);        System.out.println("double value: " + aDouble);        BigDecimal aString = new BigDecimal("1.22");        System.out.println("String value: " + aString);    }};

输出结果:

double value:1.2199999999999999733546474089962430298328399658203125String value: 1.22

是的,你没看错。

JDK的描述是这样的: 1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。3、当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。

所以,这里就要求,我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点。

即便如此,如果依然有一个误区,很可能会踏进去。

BigDecimal a =new BigDecimal("1.22");System.out.println("construct with a String value: " + a);BigDecimal b =new BigDecimal("2.22");System.out.println("construct with b String value: " + b);a.add(b); //a+bSystem.out.println("a + b =  " + a );

输出的结果是什么呢?

construct with a String value: 1.22construct with b String value: 2.22a + b = 1.22

什么?竟然还是1.22,而不是3.44.

至于原因时什么,可以看源码,自己分析。我只说一点就是BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以a.add(b);虽然做了加法操作,但是a并没有保存加操作后的值。

那该如何操作呢?

不用着急,JDK中提供有现成的方法。

1 public BigDecimal add(BigDecimal value);                        //加法2 public BigDecimal subtract(BigDecimal value);                   //减法 3 public BigDecimal multiply(BigDecimal value);                   //乘法4 public BigDecimal divide(BigDecimal value);                     //除法

总结

上面说了那么多,感觉还没有进入主题。上文主要在强调一点,金额存储为double类型,存在各个坑点。所以说,金额不建议存储为double。

接下来,进入正题。我们一般的存储方式是存储为long类型的数据,精确到分。至于优点,上文中说了那么多缺点。这里就不再多说优点了。主要是,运算上就不会出现上面遇到的问题了。而只需在展示的时候,做一次转化就OK了。

0 0
原创粉丝点击