我知道这个问题已经讨论了好几次,但我对答案并不完全满意。请不要回复“Double是不准确的,你不能代表0.1!你必须使用BigDecimal"...
基本上,我正在做一个金融软件,我们需要在内存中存储大量的价格。BigDecimal太大,无法放入缓存,因此我们决定切换到双倍。到目前为止,我们没有遇到任何bug,这是有充分理由的,我们需要12位数字的准确性。12位数的估计是基于这样一个事实,即使我们以百万计算,我们仍然能够处理美分。
双精度表示15位有效的小数位精度。如果你必须显示/比较你的双打,你会出什么问题?
我猜问题是不准确的累积,但它有多糟糕?要做多少次运算才能影响到第12位?
你觉得替身还有什么其他问题吗?
编辑:关于long,这绝对是我们考虑过的事情。我们正在做很多除法乘法,long不能很好地处理它(丢失小数和溢出),或者至少你必须非常非常小心地处理你所做的事情。我的问题更多的是关于替身理论,基本上它有多糟糕,不准确是可以接受的吗?
EDIT2:不要试图解决我的软件,我可以接受不准确:)。我重述了这个问题:如果你只需要12位数,并且在显示/比较时四舍五入加倍,那么发生不准确的可能性有多大?
发布于 2013-11-14 21:04:03
如果您绝对不能使用BigDecimal,并且不希望使用doubles,那么可以使用longs来执行fixed-point arithmetic (例如,每个long值将表示美分的数量)。这将允许您表示18个有效数字。
我会说使用joda-money,但这是在幕后使用BigDecimal。
编辑(因为上面并没有真正回答这个问题):
免责声明:如果准确性对你很重要,don't use double to represent money。但这张海报似乎不需要精确的准确性(这似乎是关于一个可能具有超过10**-12内置不确定性的金融定价模型),而更关心的是性能。假设是这种情况,使用double是情有可原的。
通常,double不能精确地表示小数。那么,double有多不精确呢?这个问题没有简单的答案。
double可能能够很好地表示一个数字,以至于您可以将该数字读入double,然后再将其写回,同时保留15位小数位的精度。但由于它是二进制而不是十进制分数,它不可能是精确的-它是我们希望表示的值,加上或减去一些误差。当执行涉及不精确小数位(double)的许多算术运算时,该误差的量可能随着时间的推移而累积,使得最终产品具有少于15个十进制数字的精度。少了多少?这要看情况了。
考虑以下函数,该函数取1000的n次根,然后将其与自身相乘n次:
private static double errorDemo(int n) {
double r = Math.pow(1000.0, 1.0/n);
double result = 1.0;
for (int i = 0; i < n; i++) {
result *= r;
}
return 1000.0 - result;
}结果如下:
errorDemo( 10) = -7.958078640513122E-13
errorDemo( 31) = 9.094947017729282E-13
errorDemo( 100) = 3.410605131648481E-13
errorDemo( 310) = -1.4210854715202004E-11
errorDemo( 1000) = -1.6370904631912708E-11
errorDemo( 3100) = 1.1107204045401886E-10
errorDemo( 10000) = -1.2255441106390208E-10
errorDemo( 31000) = 1.3799308362649754E-9
errorDemo( 100000) = 4.00075350626139E-9
errorDemo( 310000) = -3.100740286754444E-8
errorDemo(1000000) = -9.706695891509298E-9请注意,累积误差的大小并不完全与中间步骤的数量成比例增加(实际上,它不是单调增加的)。给定一系列已知的中间操作,我们可以确定不准确性的概率分布;虽然这将具有更大的范围,但确切的数量将取决于输入到计算中的数字。不确定性本身就是不确定的!
根据您正在执行的计算类型,您可以通过在中间步骤后四舍五入到整单位/整分来控制此误差。(考虑一个持有100美元的银行账户,每月复利6%,所以每月0.5%的利息。第三个月的利息记入贷方后,余额是101.50美元还是101.51美元?)让你的double代表小数单位的数量(即美分),而不是整数单位的数量,这会让这件事变得更容易-但如果你这样做了,你也可以像我上面建议的那样使用longs。
免责声明再次声明:浮点错误的累积使得使用double来获取大量资金可能会变得相当混乱。作为一名Java开发人员,多年来一直使用double表示十进制来表示任何东西,对于任何涉及金钱的重要计算,我都会使用十进制而不是浮点算法。
发布于 2013-11-14 21:04:25
Martin Fowler写了一篇关于这个话题的文章。他建议使用内部长表示法和小数因子的货币类。http://martinfowler.com/eaaCatalog/money.html
发布于 2013-11-14 21:22:51
如果不使用定点(整型)算法,你就不能确保你的计算总是正确的。这是因为IEEE 754浮点表示的工作方式,某些十进制数不能表示为有限长度的二进制小数。但是,所有定点数字都可以表示为有限长度的整数;因此,它们可以存储为精确的二进制值。
请考虑以下几点:
public static void main(String[] args) {
double d = 0.1;
for (int i = 0; i < 1000; i++) {
d += 0.1;
}
System.out.println(d);
}这将打印100.09999999999859。任何使用double的money实现都将失败。
有关更直观的解释,请单击decimal to binary converter并尝试将0.1转换为二进制。你最终得到0.000110011001100110011001100110011001100110011001100110011001 (0011重复),将它转换回十进制,你得到0.0999999998603016138。
因此0.1 == 0.0999999998603016138
顺便提一下,BigDecimal只是一个带有整数小数位置的BigInteger。BigInteger依靠底层的int[]来保存其数字,因此提供了定点精度。
public static void main(String[] args) {
double d = 0;
BigDecimal b = new BigDecimal(0);
for (long i = 0; i < 100000000; i++) {
d += 0.1;
b = b.add(new BigDecimal("0.1"));
}
System.out.println(d);
System.out.println(b);
}输出:
9999999.98112945 ( 10^8加法后丢失一整分)
10000000.0
https://stackoverflow.com/questions/19978294
复制相似问题