我知道这个问题已经多次讨论过,但我对答案并不完全满意。请不要回答"双打是不准确的,你不能代表0.1!你必须使用BigDecimal" ...
基本上我正在做一个财务软件,我们需要在内存中存储很多价格。 BigDecimal太大了,无法容纳缓存,因此我们决定切换到double。 到目前为止,我们没有因为正当理由而遇到任何错误,我们需要12位数的准确度。 12位数的估算是基于这样一个事实:即使我们以百万谈话,我们仍然可以处理美分。
double给出15位有效十进制数字的精度。如果你必须显示/比较它们时你的双打,可能会出现什么问题?
我猜问题是不准确的积累,但它有多糟糕?在它影响第12位之前需要多少次操作?
你看到双打还有其他问题吗?
编辑:很长一段时间,这绝对是我们所想到的。我们正在进行大量的除法乘法,并且很久不会很好地处理(丢失小数和溢出),或者至少你必须非常小心你所做的事情。我的问题更多的是关于双打理论,基本上有多糟糕,是否可以接受不准确?EDIT2:不要试图解决我的软件,我不准确:)。我重新提出这样的问题:如果你只需要12个数字并且在显示/比较时你会翻倍,会出现不准确的可能性吗?
答案 0 :(得分:12)
如果您绝对无法使用BigDecimal
并且不想使用double
,请使用long
来fixed-point arithmetic(因此每个long
例如,值将代表分数。这将使你代表18位有效数字。
我会说使用joda-money,但这会使用BigDecimal
。
编辑(因为上面没有真正回答这个问题):
免责声明:如果准确性对您很重要,请don't use double
to represent money。但似乎海报并不需要准确的准确性(这似乎是关于金融定价模型,可能有超过10 ** - 12内置的不确定性),并且更关心性能。假设是这种情况,使用double
是可以原谅的。
通常,double
不能精确地表示小数。那么,double
有多么不精确?对此没有简短的答案。
double
可能能够很好地表示一个数字,您可以将数字读入double
,然后再将其写回,保留精度的十五位小数。但由于它是二进制而不是小数,它不可能是精确的 - 它是我们希望表示的值,加上或减去一些错误。当执行涉及不精确double
s的许多算术运算时,该错误的量可能随着时间的推移而累积,使得最终产品的精度小于十五位数。少了几个?这取决于。
考虑以下函数,该函数采用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美元?)拥有你的{{ 1}}表示分数单位的数量(即分数),而不是整数单位的数量会使这更容易 - 但如果你这样做,你也可以只使用上面我建议的double
免责声明,再次:浮点错误的积累使得使用long
s的金额可能非常混乱。作为一个Java开发人员,他曾经使用double
来表达多年来对他进行过任何操作的十进制表示,我会使用十进制而不是浮点运算来计算任何涉及金钱的重要计算。
答案 1 :(得分:5)
Martin Fowler在这个主题上写了一些东西。他建议使用具有内部长表示的Money类和小数因子。 http://martinfowler.com/eaaCatalog/money.html
答案 2 :(得分:5)
不使用定点(整数)算术,您无法确定计算是否始终正确。这是因为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
WILL 的任何资金实施失败。
要获得更直观的解释,请点击decimal to binary converter并尝试将0.1转换为二进制。你最终得到0.00011001100110011001100110011001(0011重复),将它转换回小数你得到0.0999999998603016138。
因此0.1 == 0.0999999998603016138
作为旁注,BigDecimal只是一个带有int小数位的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
答案 3 :(得分:1)
如果BigDecimal
的大小对于您的缓存来说太大,那么在将数据写入缓存时应将金额转换为long
值,并在它们转换为BigDecimal
时将其转换为BigDecimal
正在阅读这将为您的缓存提供更小的内存占用空间,并在您的应用程序中进行准确的计算。
即使您能够使用双精度正确表示您对计算的输入,但这并不意味着您将始终获得准确的结果。您仍然可能会遇到cancellation等事情。
如果您拒绝使用BigDecimal
作为应用程序逻辑,那么您将重写{{1}}已经提供的许多功能。
答案 4 :(得分:1)
从历史上看,使用浮点类型对整数进行精确计算通常是合理的,这可能会大于2 ^ 32,但不会大于2 ^ 52 [或者,在具有适当“长双”类型的机器上,2 ^ 64]。将52位数字除以32位数以产生20位商需要在8088上进行相当冗长的绘制过程,但8087处理器可以相对快速,轻松地完成。如果所有需要精确的值始终用整数表示,那么使用小数进行财务计算是完全合理的。
如今,计算机能够更有效地处理更大的整数值,因此使用整数来处理将由整数表示的数量通常更有意义。对于像分数除法这样的事情,浮点似乎很方便,但正确的代码必须处理将事物舍入到整数的效果,无论它做什么。如果三个人需要支付100美元的费用,那么每个人支付33.333333333333就无法实现一分钱一分钱的会计。使事情保持平衡的唯一方法就是让人民支付不平等的金额。
答案 5 :(得分:0)
我将通过解决问题的不同部分来回答问题。请接受我试图解决根本问题,而不是状态问题。你有没有看过减少内存的所有选项?
查看存储问题并停止寻找避免潜在的数学问题。通常,在您不得不担心数字之前,Java中有很多过剩的东西。甚至有些人可以通过上述想法解决这些问题。
答案 6 :(得分:-1)
你不能相信财务软件中的双打。它们可能在简单的情况下工作得很好,但由于四舍五入,呈现某些值等不准确,你会遇到问题。
您别无选择,只能使用BigDecimal
。否则你会说“我正在编写几乎有效的财务代码。你几乎不会注意到任何差异。”而那不会让你看起来值得信赖。
固定点在某些情况下有效,但您能确定现在和将来1美分的准确度是否足够?
答案 7 :(得分:-1)
我希望你读过Joshua Bloch Java Puzzlers Traps陷阱。这就是他在谜题2中所说的:改变的时候。
二进制 浮点数特别不适合货币计算,因为它无法代表 0.1-或任何其他10的负幂 - 正如有限长度二进制分数[EJ项31]。