Java 使用Double开发财务软件

Java 使用Double开发财务软件,java,floating-point,double,bigdecimal,ieee-754,Java,Floating Point,Double,Bigdecimal,Ieee 754,我知道这个问题已经讨论过好几次了,但我对答案并不完全满意。请不要回答“双精度不准确,不能代表0.1!必须使用BigDecimal” 基本上我在做一个金融软件,我们需要在内存中存储很多价格。BigDecimal太大,无法放入缓存,因此我们决定切换到double。 到目前为止,我们没有遇到任何错误的好理由,我们需要一个12位的准确性。12位数的估计是基于这样一个事实,即即使我们以百万为单位说话,我们仍然能够处理美分 双精度表示15位小数的精度。如果在必须显示/比较双打时将双打四舍五入,会出现什么问题

我知道这个问题已经讨论过好几次了,但我对答案并不完全满意。请不要回答“双精度不准确,不能代表0.1!必须使用BigDecimal”

基本上我在做一个金融软件,我们需要在内存中存储很多价格。BigDecimal太大,无法放入缓存,因此我们决定切换到double。 到目前为止,我们没有遇到任何错误的好理由,我们需要一个12位的准确性。12位数的估计是基于这样一个事实,即即使我们以百万为单位说话,我们仍然能够处理美分

双精度表示15位小数的精度。如果在必须显示/比较双打时将双打四舍五入,会出现什么问题

我猜问题在于不准确的积累,但它有多糟糕?需要多少次操作才能影响第12位数字

你认为双打还有其他问题吗

编辑:大概很长,这绝对是我们一直在思考的问题。我们正在做大量的除法乘法运算,long无法很好地处理这一问题(丢失小数点和溢出),或者至少您必须非常小心地处理您所做的事情。我的问题更多的是关于双打理论,基本上它有多糟糕,误差可以接受吗


EDIT2:不要试图解决我的软件问题,我对不准确的问题很好:)。我重申了这个问题:如果您只需要12位数字,并且在显示/比较时四舍五入双倍,那么发生不准确的可能性有多大?

如果您绝对不能使用
BigDecimal
,并且不希望使用
double
s,请使用
long
s(例如,每个
long
值将表示美分数)。这将允许您表示18个有效数字

我会说使用,但这在封面下使用了
BigDecimal


编辑(因为上面没有真正回答问题):

免责声明:如果准确性对你来说很重要,请不要这样做。但是,海报似乎不需要精确的准确性(这似乎是关于一个金融定价模型,它可能有10**-12个内置的不确定性),并且更关心性能。假设是这样,使用
双精度
是情有可原的

一般来说,
double
不能精确地表示小数点。因此,
double
有多不精确?没有简单的答案

double
可能能够很好地表示一个数字,您可以将该数字读入一个
double
,然后重新写出来,保留15位小数精度。但由于它是一个二进制数字,而不是一个小数,所以它不可能是精确的-它是我们希望表示的值,加上或减去一些错误。当许多算术运算都涉及不精确的
double
s,随着时间的推移,此错误的数量会逐渐增加,因此最终产品的精度小于15位小数。减少多少?这取决于

考虑以下函数,该函数取1000的
n
th根,然后将其自身乘以
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;
}
请注意,累积误差的大小不会与中间步骤的数量成正比地增加(事实上,它不是单调增加的).给定一系列已知的中间运算,我们可以确定不准确度的概率分布;虽然这将有一个更大的范围,但运算越多,准确的数量将取决于输入计算的数字。不确定度本身是不确定的

根据您正在执行的计算类型,您可以通过在中间步骤后四舍五入到整单位/整美分来控制此错误。(假设一个银行账户持有100美元,月复利为6%,因此每月利息为0.5%。在第三个月的利息被贷记后,您希望余额为101.50美元还是101.51美元?)将您的
双倍
表示分数单位数(即美分)而不是整单位的数量会使这更容易-但如果你这样做,你可以只使用
long
s,就像我上面建议的那样


免责声明:浮点错误的累积使得使用
double
s来表示金额可能相当混乱。作为一名Java开发人员,多年来一直使用
double
来表示灌输给他的任何内容的十进制表示法,我会使用十进制而不是浮点算术来表示错误马丁·福勒(Martin Fowler)在这个主题上写了一些东西。他建议使用一个具有内部长表示形式和十进制因子的money类。

在金融软件中,您不能信任Double。在简单的情况下,Double可能非常有效,但由于舍入、某些值的表示不准确等原因,您将遇到问题

你别无选择,只能使用
BigDecimal
。否则,你会说“我正在编写财务代码,几乎可以正常工作。你几乎不会注意到任何差异。”而这并不能让你看起来可信


定点在某些情况下是有效的,但是你能确定现在和将来1美分的准确度就足够了吗?

我希望你读过Joshua Bloch Java谜题陷阱。这是他在谜题2:改变的时候了

二进制的 浮点数特别不适合于货币计算,因为它无法表示 0.1-或10的任何其他负幂-与有限长度二进制分数完全相同[EJ第31项]


如果
BigDecimal
的大小对于缓存来说太大,则在将金额写入缓存时应将其转换为
long
值,并在读取时将其转换回
BigDecimal
。这将
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
public static void main(String[] args) {
    double d = 0.1;
    for (int i = 0; i < 1000; i++) {
        d += 0.1;
    }
    System.out.println(d);
}
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);
}