Double vs. BigDecimal?

问题描述 投票:249回答:8

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

java floating-point double bigdecimal
8个回答
390
投票

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

BigDecimal的缺点是速度较慢,而且编程算法更难(因为+ - */没有超载)。

如果您正在处理金钱,或精确度是必须的,请使用BigDecimal。否则Doubles往往足够好。

我建议阅读javadocBigDecimal,因为他们解释的事情比我在这里做得更好:)


147
投票

我的英语不好,所以我在这里写一个简单的例子。

    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

有人还想用双? ;)


43
投票

与double有两个主要区别:

  • 任意精度,与BigInteger类似,它们可以包含任意精度和大小的数量
  • 基数10而不是基数2,BigDecimal是n * 10 ^ scale,其中n是任意大的有符号整数,而scale可以被认为是向左或向右移动小数点的位数

您应该使用BigDecimal进行货币计算的原因并不是它可以表示任何数字,而是它可以表示可以用十进制概念表示的所有数字,并且几乎包括货币世界中的所有数字(您永远不会转移1/3 $给某人)。


19
投票

如果你写下像1 / 7这样的小数值作为十进制值

1/7 = 0.142857142857142857142857142857142857142857...

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

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

1/10 = binary 0.0001100110011001100110011001100110...

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

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

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


7
投票

BigDecimal是Oracle的任意精度数值库。 BigDecimal是Java语言的一部分,适用于从财务到科学的各种应用程序(这就是上下文)。

将双打用于某些计算没有任何问题。但是,假设您想要计算Math.Pi * Math.Pi / 6,即Riemann Zeta函数的值,实际参数为2(我正在研究的项目)。浮点除法会给您带来一个圆角误差的痛苦问题。

另一方面,BigDecimal包含许多用于计算任意精度的表达式的选项。下面的Oracle文档中描述的add,multiply和divide方法在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方法在这些方面很重要。


6
投票

如果您正在处理计算,则有关于如何计算以及应使用何种精度的法律。如果你失败了,你会做一些违法的事情。唯一真正的原因是十进制情况的位表示不精确。罗勒只是简单地说,一个例子是最好的解释。只是为了补充他的例子,这是发生的事情:

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 

-8
投票

原始数字类型对于在内存中存储单个值很有用。但是当使用double和float类型处理计算时,舍入存在问题。因为内存表示没有精确映射到值,所以会发生这种情况。例如,double值应该占用64位,但Java不使用所有64位。它只存储它认为数字的重要部分。因此,当您将float或double类型的值一起添加时,可能会出现错误的值。可能是这个视频https://youtu.be/EXxUSz9x7BM将解释更多

© www.soinside.com 2019 - 2024. All rights reserved.