我必须计算一些浮点变量,我的同事建议我使用BigDecimal
而不是double
因为它会更精确。但我想知道它是什么以及如何充分利用BigDecimal
?
6 回答
ABigDecimal
是表示数字的精确方式。ADouble
具有一定的精度。使用各种大小的双精度数(例如d1=1000.0
和d2=0.001
)可能会导致在0.001
求和时一起被丢弃,因为大小的差异如此之大。BigDecimal
不会发生这种情况。
的缺点BigDecimal
是它速度较慢,并且以这种方式编程算法有点困难(由于+
-
*
而/
不是过载)。
如果您正在处理金钱,或者必须精确,请使用BigDecimal
. 否则Doubles
往往会足够好。
我确实建议阅读javadoc,BigDecimal
因为他们确实比我在这里做的更好:)
我的英语不好,所以我在这里只写一个简单的例子。
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 有两个主要区别:
- 任意精度,与 BigInteger 类似,它们可以包含任意精度和大小的数量(而 double 具有固定数量的位)
- 以 10 为底而不是以 2 为底,BigDecimal 是
n*10^-scale
其中 n 是任意大的有符号整数,并且可以将比例视为向左或向右移动小数点的位数
说 BigDecimal 可以表示任何数字仍然是不正确的。但是您应该使用 BigDecimal 进行货币计算的两个原因是:
- 它可以表示所有可以用十进制表示的数字,并且几乎包括货币世界中的所有数字(你永远不会将 1/3 美元转移给某人)。
- 可以控制精度以避免累积误差。对于 a
double
,随着值大小的增加,它的精度会降低,这会给结果带来很大的误差。
如果你写下一个小数值,比如1 / 7
十进制值,你会得到
1/7 = 0.142857142857142857142857142857142857142857...
有一个无限序列142857
。由于您只能写入有限数量的数字,因此不可避免地会引入舍入(或截断)错误。
类似1/10
或1/100
表示为带有小数部分的二进制数的数字在小数点后也有无限位数:
1/10 = binary 0.0001100110011001100110011001100110...
Doubles
将值存储为二进制,因此仅通过将十进制数转换为二进制数可能会引入错误,甚至无需进行任何算术运算。
BigDecimal
另一方面,十进制数(如)按原样存储每个十进制数字(二进制编码,但每个十进制都是独立的)。这意味着十进制类型在一般意义上并不比二进制浮点或定点类型更精确(即它不能在1/7
不损失精度的情况下存储),但对于具有有限个十进制数字的数字更准确货币计算通常是这种情况。
Java 的BigDecimal
另一个优点是它可以在小数点两边有任意(但有限)位数,仅受可用内存的限制。
如果您正在处理计算,则有关于您应该如何计算以及应该使用什么精度的法律。如果你失败了,你将做一些非法的事情。唯一真正的原因是十进制大小写的位表示不精确。正如巴兹尔简单地说,一个例子是最好的解释。为了补充他的例子,下面是发生的事情:
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 的任意精度数值库。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 三分之一 = new BigDecimal("0.33333333333");
利用 1/3 的字符串表示来以指定的准确度表示无限重复的数字。舍入错误很可能在 JVM 内部的某个地方如此之深,以至于舍入错误不会干扰您的大部分实际计算。然而,从个人经验来看,我已经看到了四舍五入的情况。从 Oracle 文档中可以看出,setScale 方法在这些方面很重要。