美文网首页程序员
java中精确计算,double与BigDecimal的取舍

java中精确计算,double与BigDecimal的取舍

作者: 码语者 | 来源:发表于2019-01-23 23:49 被阅读0次

    相信java程序员都知道double是一种不能用作精确计算的类型,因为它会有精度损失,而要想规避精度损失,大家都会想到BigDecimal,这是JDK提供的类,确实能解决精度问题,但是它并不是完美的,它有如下三个缺点:

    • 也不是那么准确

    一、慢有多慢?
    所有人都知道,BigDecimal作为对象,new出来是有成本的,肯定比基本数据类型会慢一点,但具体慢到什么程度呢?我写了一段求加和的代码如下:

            double sum = 0.0;
            long a = System.currentTimeMillis();
            for (int i = 0; i < times; i++) {
                double d = Math.random();
                sum += d;
            }
            long b = System.currentTimeMillis();
            System.out.println(b - a);
            System.out.println(sum);
    
            Double sum = 0.0;
            long a = System.currentTimeMillis();
            for (int i = 0; i < times; i++) {
                Double d = Math.random();
                sum += d;
            }
            long b = System.currentTimeMillis();
            System.out.println(b - a);
            System.out.println(sum);
    
            BigDecimal sum = new BigDecimal("0");
            long a = System.currentTimeMillis();
            for (int i = 0; i < times; i++) {
                BigDecimal d = new BigDecimal(Math.random());
                sum = sum.add(d);
            }
            long b = System.currentTimeMillis();
            System.out.println(b - a);
            System.out.println(sum);
    

    这三段代码分别是用基本数据类型、包装数据类型和BigDecimal来计算一个和,我的电脑上分别运行这三段代码,当times是10000的时候,前两个是1ms执行时间,而第三个是30ms,当times是100000000的时候,前两个是差不多2秒(但是结果显然不正确),而第三个是差不多40秒,这是20~30倍的执行效率差距,所以BigDecimal绝不能滥用。至于为什么包装类的创建几乎没有感觉到呢?自动拆装箱不需要时间吗?这个就涉及到一些底层的优化策略了,在此不做深究,先了解一下结论,不用太纠结基本类型和包装类型。
    二、乱又怎样?
    乱,在某些人看来,并不能称为问题,但我是有些不能接受的,比如一个简单的物理公式:距离=速度时间+(加速度时间的平方)/2(即:S=vt+1/2at^2),用double来写就跟公式本身很像,而用BigDecimal,即使是这样简单的公式,也让人看得云里雾里,不知所云,代码如下:

            double s;
            double v = 2323.346852;
            double a = 102.1523684;
            double t = 20.004;
            s = v * t + (a * Math.pow(t, 2)) / 2;
            System.out.println(s); // 66914.87711409896
    
            BigDecimal s;
            BigDecimal v = new BigDecimal(2323.346852);
            BigDecimal a = new BigDecimal(102.1523684);
            BigDecimal t = new BigDecimal(20.004);
            // 写这个可得留神,稍微一个不注意,错个括号,那可就差大了,
            // 有的人可能想要提取变量,可能会好点,但很多时候就像这个例子,
            // 最复杂的部分a*t^2/2,提取出来的变量叫什么名能够见名知意?可能效果跟不提取也没啥区别。
            s = v.multiply(t).add(a.multiply(t.pow(2)).divide(new BigDecimal(2)));
            System.out.println(s); // 66914.8771140989556179216886351698979346...........还没完
    

    三、JDK的类能不准?
    关于BigDecimal计算不准确的问题,项目中已经多次遇到了,分为两方面,一方面是BigDecimal的用法不对,本文中上面所列举的所有关于BigDecimal的代码,用法都是错的;另一方面是即使用对了结果也未必是你想要的。
    先说第一个用法的问题,很多程序员在使用BigDecimal时会用BigDecimal(double val)这个构造方法,JDK的文档中说得非常明白“The results of this constructor can be somewhat unpredictable”,“The String constructor, on the other hand, is perfectly predictable”(double的构造方法是垃圾,String的构造方法是完美的),而很少有人在被这个构造方法坑掉之前查阅这个文档,我们用String构造重新执行一下算距离的公式

            BigDecimal s;
            BigDecimal v = new BigDecimal("2323.346852");
            BigDecimal a = new BigDecimal("102.1523684");
            BigDecimal t = new BigDecimal("20.004");
            s = v.multiply(t).add(a.multiply(t.pow(2)).divide(new BigDecimal("2")));
            System.out.println(s); // 66914.8771140989472(完结)
    

    可以看出,用double构造方法来计算,精度几乎没有比double提升多少,但开销多了30倍。可见对知识的一知半解有的时候比完全不懂更差。
    有的时候,现实是很残酷的,我们用了正确的方法使用了BigDecimal而结果依然可能是不尽如人意的,比如下面这个场景:

    部门 人员 考勤(分钟)
    1 Jack 3073.32
    1 Robin 3073.32
    1 小白 3073.32
    2 Robin 3073.32
    2 小白 3073.32
    2 Jack 3073.32

    假设某个公司有三个人,两个部门,而这三个人来回在这两个部门之间调动,现在要统计每个人的总考勤,每个部门的总考勤,还有整个公司的总考勤,并且是要以小时为单位,保留两位小数,这个需求一看就很明确,每个人的考勤总和、每个部门的考勤总和、公司的总考勤应该是相等的,但不幸的是,经过计算可能会得到下面的结果:

    部门 考勤(分钟) 考勤(小时) 人员 考勤(分钟) 考勤(小时)
    1 9219.96 153.67 Jack 6146.64 102.44
    2 9219.96 153.67 Robin 6146.64 102.44
    - - - 小白 6146.64 102.44
    合计 - 307.34 合计 - 307.32

    显然,每个人的考勤总和、每个部门的考勤总和已经不相等了,而这跟你用double还是BigDecimal无关。更令人绝望的是整个公司的合计是18439.92分钟,保留小数后是307.33小时,三个本应该相等的数,全都不相等。
    可能有的朋友已经看出了端倪,这不是一个技术问题,这已经是一个业务了,如果这个项目的需求就是这样的,那作为开发人员必须据理力争,痛陈这个不合理性,不要再做任何技术尝试,不管是用double还是BigDecimal都是在浪费时间。

    相关文章

      网友评论

        本文标题:java中精确计算,double与BigDecimal的取舍

        本文链接:https://www.haomeiwen.com/subject/nrdrdqtx.html