【问题标题】:Double vs. BigDecimal?Double 与 BigDecimal?
【发布时间】:2011-03-25 17:09:53
【问题描述】:

我必须计算一些浮点变量,我的同事建议我使用BigDecimal 而不是double,因为它会更精确。但我想知道它是什么以及如何充分利用BigDecimal

【问题讨论】:

标签: java floating-point double bigdecimal


【解决方案1】:

与double有两个主要区别:

  • 任意精度,与 BigInteger 类似,它们可以包含任意精度和大小的数量(而 double 具有固定位数)
  • 以 10 为底而不是以 2 为底,BigDecimal 是 n*10^-scale,其中 n 是任意大的有符号整数,并且可以将小数位视为向左或向右移动小数点的位数

说 BigDecimal 可以表示任何数字仍然是不正确的。但是您应该使用 BigDecimal 进行货币计算的两个原因是:

  • 它可以表示所有可以用十进制表示的数字,并且几乎包括货币世界中的所有数字(您永远不会将 1/3 美元转移给某人)。
  • 可以控制精度以避免累积误差。对于double,随着值的幅度增加,其精度会降低,这可能会给结果带来很大的误差。

【讨论】:

【解决方案2】:

我的英文不好,这里只写一个简单的例子。

double a = 0.02;
double b = 0.03;
double c = b - a;
System.out.println(c);

BigDecimal _a = new BigDecimal("0.02");
BigDecimal _b = new BigDecimal("0.03");
BigDecimal _c = _b.subtract(_a);
System.out.println(_c);

程序输出:

0.009999999999999998
0.01

还有人想用双吗? ;)

【讨论】:

  • @eldjon 这不是真的,看这个例子: BigDecimal two = new BigDecimal("2"); BigDecimal 八 = new BigDecimal("8"); System.out.println(two.divide(八));这打印出 0.25。
  • 尽管如此,如果您使用浮点数,在这种情况下您将获得与 BigDecimal 相同的精度,但性能要好得多
  • @EliuX Float 可能适用于 0.03-0.02,但其他值仍然不精确:System.out.println(0.003f - 0.002f);BigDecimal 是精确的:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
【解决方案3】:

如果你写下一个像1 / 7 这样的小数值作为十进制值,你会得到

1/7 = 0.142857142857142857142857142857142857142857...

具有142857 的无限序列。由于您只能写入有限数量的数字,因此不可避免地会引入舍入(或截断)错误。

1/101/100 这样的数字表示为带有小数部分的二进制数,在小数点后也有无限位数:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles 将值存储为二进制,因此可能仅通过将十进制数转换为二进制数而引入错误,甚至不进行任何算术运算。

另一方面,十进制数字(如BigDecimal)按原样存储每个十进制数字(二进制​​编码,但每个十进制都是独立的)。这意味着十进制类型在一般意义上并不比二进制浮点或定点类型更精确(即它不能在不损失精度的情况下存储1/7),但对于具有有限个数的数字更准确十进制数字,这通常用于货币计算。

Java 的BigDecimal 有一个额外的优势,它可以在小数点两边有任意(但有限)位数,仅受可用内存限制。

【讨论】:

    【解决方案4】:

    如果您正在处理计算,则有一些关于您应该如何计算以及应该使用什么精度的法律。如果你失败了,你将做一些非法的事情。 唯一真正的原因是十进制大小写的位表示不精确。正如巴兹尔简单地说,一个例子是最好的解释。为了补充他的例子,下面是发生的事情:

    static void theDoubleProblem1() {
        double d1 = 0.3;
        double d2 = 0.2;
        System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));
    
        float f1 = 0.3f;
        float f2 = 0.2f;
        System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));
    
        BigDecimal bd1 = new BigDecimal("0.3");
        BigDecimal bd2 = new BigDecimal("0.2");
        System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
    }
    

    输出:

    Double:  0,3 - 0,2 = 0.09999999999999998
    Float:   0,3 - 0,2 = 0.10000001
    BigDec:  0,3 - 0,2 = 0.1
    

    我们也有:

    static void theDoubleProblem2() {
        double d1 = 10;
        double d2 = 3;
        System.out.println("Double:\t 10 / 3 = " + (d1 / d2));
    
        float f1 = 10f;
        float f2 = 3f;
        System.out.println("Float:\t 10 / 3 = " + (f1 / f2));
    
        // Exception! 
        BigDecimal bd3 = new BigDecimal("10");
        BigDecimal bd4 = new BigDecimal("3");
        System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
    }
    

    给我们输出:

    Double:  10 / 3 = 3.3333333333333335
    Float:   10 / 3 = 3.3333333
    Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion
    

    但是:

    static void theDoubleProblem2() {
        BigDecimal bd3 = new BigDecimal("10");
        BigDecimal bd4 = new BigDecimal("3");
        System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
    }
    

    有输出:

    BigDec:  10 / 3 = 3.3333 
    

    【讨论】:

    • 该死,你能想象警察在凌晨 2 点冲进你的门吗……“先生,这是你的密码吗?你知道你用错误的精度来划分这两个数字吗?!反对墙,现在”
    • @Tarek7 对于银行、市场、电信等任何与金钱相关的计算而言,这确实是一个法律问题。如果你看过超人,你就会明白,一个简单的精度变化就能让你成为百万富翁! :)
    【解决方案5】:

    BigDecimal 是表示数字的精确方式。 Double 有一定的精度。使用不同大小的双精度数(比如d1=1000.0d2=0.001)可能会导致在求和时0.001 一起被丢弃,因为大小差异如此之大。 BigDecimal 不会发生这种情况。

    BigDecimal 的缺点是速度较慢,并且以这种方式编写算法有点困难(由于 + - */ 没有被重载)。

    如果您正在处理金钱问题,或者需要精确,请使用BigDecimal。否则Doubles 往往就足够了。

    我确实建议阅读BigDecimaljavadoc,因为他们确实比我在这里做的更好:)

    【讨论】:

    • 是的,我正在计算股票的价格,所以我相信 BigDecimal 在这种情况下很有用。
    • @Truong Ha:处理价格时,您想使用 BigDecimal。如果您将它们存储在数据库中,您需要类似的东西。
    • 说“BigDecimal 是一种精确的数字表示方式”是一种误导。 1/3 和 1/7 不能在以 10 为底的数字系统(BigDecimal)或以 2 为底的数字系统(浮点数或双精度数)中精确表示。 1/3 可以精确地表示为 base 3、base 6、base 9、base 12 等,而 1/7 可以精确地表示为 base 7、base 14、base 21 等。BigDecimal 的优点是它是任意精度并且人类已经习惯了以 10 为底的舍入误差。
    • 关于它变慢的好点,帮助我理解为什么 Netflix 功能区负载均衡器代码处理双精度,然后有这样的行:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
    • @extraneon 我想你的意思是说“如果 accuracy 是必须的,请使用BigDecimal”,Double 会有更多的“精度”(更多位数)。
    【解决方案6】:

    BigDecimal 是 Oracle 的任意精度数值库。 BigDecimal 是 Java 语言的一部分,可用于从金融到科学的各种应用程序(这就是我的意思)。

    在某些计算中使用双精度并没有错。但是,假设您想计算 Math.Pi * Math.Pi / 6,即 Riemann Zeta 函数对于实参为 2 的值(我目前正在从事的项目)。浮点除法会给您带来一个痛苦的舍入误差问题。

    另一方面,BigDecimal 包含许多用于将表达式计算到任意精度的选项。下面 Oracle 文档中描述的加法、乘法和除法方法“取代”了 BigDecimal Java World 中的 +、* 和 /:

    http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

    compareTo 方法在 while 和 for 循环中特别有用。

    但是,在使用 BigDecimal 的构造函数时要小心。字符串构造函数在很多情况下都非常有用。比如代码

    BigDecimal onethird = new BigDecimal("0.33333333333");

    使用 1/3 的字符串表示来以指定的准确度表示无限重复的数字。舍入误差很可能在 JVM 内部的某个地方如此之深,以至于舍入误差不会干扰您的大部分实际计算。然而,从个人经验来看,我已经看到了四舍五入的情况。从 Oracle 文档中可以看出,setScale 方法在这些方面很重要。

    【讨论】:

    • BigDecimal 是 Java 的 任意精度数值库的部分。在这种情况下,“内部”是毫无意义的,尤其是它是由 IBM 编写的。
    • @EJP:我查看了 BigDecimal 类并了解到它只有一部分是由 IBM 编写的。下方版权评论:/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多