首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将Double用于财务软件

将Double用于财务软件
EN

Stack Overflow用户
提问于 2013-11-14 21:00:30
回答 8查看 1.6K关注 0票数 3

我知道这个问题已经讨论了好几次,但我对答案并不完全满意。请不要回复“Double是不准确的,你不能代表0.1!你必须使用BigDecimal"...

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

双精度表示15位有效的小数位精度。如果你必须显示/比较你的双打,你会出什么问题?

我猜问题是不准确的累积,但它有多糟糕?要做多少次运算才能影响到第12位?

你觉得替身还有什么其他问题吗?

编辑:关于long,这绝对是我们考虑过的事情。我们正在做很多除法乘法,long不能很好地处理它(丢失小数和溢出),或者至少你必须非常非常小心地处理你所做的事情。我的问题更多的是关于替身理论,基本上它有多糟糕,不准确是可以接受的吗?

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

EN

回答 8

Stack Overflow用户

发布于 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次:

代码语言:javascript
复制
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;
}

结果如下:

代码语言:javascript
复制
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表示十进制来表示任何东西,对于任何涉及金钱的重要计算,我都会使用十进制而不是浮点算法。

票数 13
EN

Stack Overflow用户

发布于 2013-11-14 21:04:25

Martin Fowler写了一篇关于这个话题的文章。他建议使用内部长表示法和小数因子的货币类。http://martinfowler.com/eaaCatalog/money.html

票数 6
EN

Stack Overflow用户

发布于 2013-11-14 21:22:51

如果不使用定点(整型)算法,你就不能确保你的计算总是正确的。这是因为IEEE 754浮点表示的工作方式,某些十进制数不能表示为有限长度的二进制小数。但是,所有定点数字都可以表示为有限长度的整数;因此,它们可以存储为精确的二进制值。

请考虑以下几点:

代码语言:javascript
复制
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[]来保存其数字,因此提供了定点精度。

代码语言:javascript
复制
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

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19978294

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档