我需要计算一些浮点变量,我的同事建议我使用 BigDecimal
而不是 double
,因为它会更精确。但我想知道它是什么以及如何充分利用 BigDecimal
?
我需要计算一些浮点变量,我的同事建议我使用 BigDecimal
而不是 double
,因为它会更精确。但我想知道它是什么以及如何充分利用 BigDecimal
?
BigDecimal
是一种精确表示数字的方式。而Double
则具有一定的精度。当使用不同量级的double(例如:d1 = 1000.0
和d2 = 0.001
)时,由于差异较大,可能会导致0.001
在求和时被完全舍去。但是使用BigDecimal
就不会出现这种情况。
BigDecimal
的缺点是速度较慢,编写算法也稍微有些困难(因为没有重载+
、-
、*
和/
)。如果您需要处理货币或者必须保证精度,则应使用BigDecimal
。否则,Double
通常已经足够了。BigDecimal
的javadoc,因为它们比我在这里解释的更好 :)if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
。 - michaelokBigDecimal
”,Double会有更多的“精度”(更多的数字)。 - jspinella我的英文不好,所以我只会写一个简单的例子。
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
还有人想要使用双倍符号吗? ;)
相对于 double 类型,BigDecimal 类型有两个主要区别:
n * 10^(-scale)
,其中 n 是任意大的有符号整数,scale 可以被视为将小数点向左或向右移动的位数。尽管 BigDecimal 仍然不能表示所有数字,但在货币计算中使用 BigDecimal 的两个原因是:
double
类型,随着值的增加,其精度会降低,从而可能导致结果中出现重大误差。1 / 7
,写成小数形式,你会得到1/7 = 0.142857142857142857142857142857142857142857...
1/10 = binary 0.0001100110011001100110011001100110...
BigDecimal
的情况下,以x*10y的形式存储为两个带符号的二进制整数(感谢@AlexSalauyou的指出)。参见:Class BigDecimal。这意味着十进制类型在一般意义上并不比二进制浮点数或定点数更精确(即不能存储1/7
而不丢失精度),但对于具有有限十进制位数的数字,它更准确,而这在货币计算中经常发生。BigDecimal
还具有额外的优势,它可以在小数点两侧拥有任意(但有限)数量的数字,仅受可用内存的限制。BigDecimal
以x*10^y
的形式存储数字,x
和y
都以有符号的二进制整数形式存储。 - undefined如果你处理计算,那么有关于如何计算以及使用何种精度的法律。如果你违反了这些规定,你将会从事非法行为。 唯一的真正原因是十进制数字的位表示不是精确的。就像 Basil 所说的,举个例子是最好的解释。为了补充他的例子,这里是发生的情况:
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
BigDecimal 是 Oracle 的任意精度数字库,是 Java 语言的一部分,适用于各种应用程序,从金融到科学(那就是我所在的领域)。
对于某些计算使用 double 没有问题。但是,假设您想计算 Math.Pi * Math.Pi / 6,也就是针对实数参数为二的黎曼ζ函数的值(这是我目前正在进行的项目)。浮点除法会带来一个痛苦的问题,即舍入误差。
另一方面,BigDecimal 包括许多选项以任意精度计算表达式。如下所述的 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 中,不会干扰大多数实际计算。然而,从个人经验来看,我见过舍入误差的问题。setScale 方法在这些方面很重要,如 Oracle 文档所示。
new BigDecimal("0.3333")
时,小数0.3333被精确地存储。 - undefined常常有人说,累积的不准确性不足以影响结果,因为双精度浮点数的精度为1e-16(即:|delta| < 1e-16)。这对于x约为1的情况是正确的。对于x约为1M,精度为1e-10;对于100B(这是印尼盾中相当典型的金额),精度降至1e-5。现在,您有一个限制,即保证在安全区域内进行100K个简单算术运算。
即使您绝对确定累积的不准确性不会传播到结果的重要部分,您仍然必须实施一些规则来抑制它:四舍五入,从分转换等等,每当结果应被视为十进制时,即进行比较,打印,保存到数据库,发送到下游等等。而且,您的同事必须审查所有这些。
使用BigDecimal
,其中十进制小数被准确地存储,鉴于现代世界中所有货币金额都是以小数计算的,您和您的同事根本不必担心这个问题。
(divide(BigDecimal) method)
对于处理重复小数有些无用,即除数为非循环小数会抛出java.lang.ArithmeticException: Non-terminating decimal expansion;
没有确切可表示的十进制结果。
只需尝试BigDecimal.ONE.divide(new BigDecimal("3"));
另一方面,double会正确处理除法(具有约15个有效数字的预期精度)
divide(BigDecimal divisor, int scale, int roundingMode)
有何作用?它可以让我们指定除法结果的精确度,以满足我们的需求。 - ivan.ukr