我可以看到四种基本方法,这些方法
double可以在处理货币计算时给您带来麻烦。尾数太小
尾数的精度约为15位十进制数字,那么您每次处理大于该数量的值时都会得到错误的结果。如果您跟踪美分,则问题将在10 13(十万亿)美元之前开始出现。
虽然这是一个很大的数字,但并不是 那么大 。美国约18万亿的GDP超过了它的总和,因此任何与国家或什至是企业规模的交易都容易得到错误的答案。
此外,还有许多方法可以使较小的数量在计算过程中超过此阈值。您可能正在进行增长预测,或者进行了多年的增长预测,因此最终价值很大。您可能正在进行“假设分析”场景分析,其中检查了各种可能的参数,并且参数的某种组合可能会导致很大的值。您可能在财务规则下工作,该规则允许一分之几的零头可能会使您的价格范围再减少两个数量级或更多,这使您大致与美元中的个人财富保持一致。
最后,让我们不要以美国为中心。那其他货币呢?一美元的价值约合
13,000印尼盾,因此您需要另外2个数量级来跟踪该货币的货币金额(假设没有“美分”!)。您几乎要降低到凡人感兴趣的金额。
这是一个示例,其中从1e9开始于5%的增长预测计算出错:
method year amountdeltadouble 0 $ 1,000,000,000.00Decimal 0 $ 1,000,000,000.00 (0.0000000000)double 10 $ 1,628,894,626.78Decimal 10 $ 1,628,894,626.78 (0.0000004768)double 20 $ 2,653,297,705.14Decimal 20 $ 2,653,297,705.14 (0.0000023842)double 30 $ 4,321,942,375.15Decimal 30 $ 4,321,942,375.15 (0.0000057220)double 40 $ 7,039,988,712.12Decimal 40 $ 7,039,988,712.12 (0.0000123978)double 50 $ 11,467,399,785.75Decimal 50 $ 11,467,399,785.75 (0.0000247955)double 60 $ 18,679,185,894.12Decimal 60 $ 18,679,185,894.12 (0.0000534058)double 70 $ 30,426,425,535.51Decimal 70 $ 30,426,425,535.51 (0.0000915527)double 80 $ 49,561,441,066.84Decimal 80 $ 49,561,441,066.84 (0.0001678467)double 90 $ 80,730,365,049.13Decimal 90 $ 80,730,365,049.13 (0.0003051758)double 100$ 131,501,257,846.30Decimal 100$ 131,501,257,846.30 (0.0005645752)double 110$ 214,201,692,320.32Decimal 110$ 214,201,692,320.32 (0.0010375977)double 120$ 348,911,985,667.20Decimal 120$ 348,911,985,667.20 (0.0017700195)double 130$ 568,340,858,671.56Decimal 130$ 568,340,858,671.55 (0.0030517578)double 140$ 925,767,370,868.17Decimal 140$ 925,767,370,868.17 (0.0053710938)double 150 $ 1,507,977,496,053.05Decimal 150 $ 1,507,977,496,053.04 (0.0097656250)double 160 $ 2,456,336,440,622.11Decimal 160 $ 2,456,336,440,622.10 (0.0166015625)double 170 $ 4,001,113,229,686.99Decimal 170 $ 4,001,113,229,686.96 (0.0288085938)double 180 $ 6,517,391,840,965.27Decimal 180 $ 6,517,391,840,965.22 (0.0498046875)double 190 $ 10,616,144,550,351.47Decimal 190 $ 10,616,144,550,351.38 (0.0859375000)
差值(在160年
double和
BigDecimal首次点击之间的差额> 1美分,大约2万亿美元(从现在起160年可能还不够多),当然只会越来越糟。
当然,尾数的53位意味着这种计算的 相对
误差可能很小(希望您不会输掉2万亿美元中超过1美分的工作)。实际上,在大多数示例中,相对误差基本上保持稳定。不过,您当然可以组织它,以便(例如)在尾数上损失精度的情况下减去两个变量,从而导致任意大的错误(请读者行使)。
因此,您认为自己很聪明,并且设法提出了一种舍入方案,该方案使您可以
double在本地JVM上使用并进行详尽的测试。继续部署它。明天或下周,或者对您而言最糟糕的时候,结果都会改变,您的把戏也将破裂。
与几乎所有其他基本语言表达式不同,当然也与整数或
BigDecimal算术不同,默认情况下,由于strictfp功能,许多浮点表达式的结果没有单一的标准定义值。平台可以自由决定是否自由使用更高精度的中间体,这些中间体可能会在不同的硬件,JVM版本等上导致不同的结果。对于相同的输入,结果在运行时甚至可能在方法从解释型转换为JIT时在运行时有所不同。编译!
如果您是在Java 1.2之前的日子里编写代码的,那么当Java
1.2突然引入现在默认的变量FP行为时,您会非常生气。您可能很想在
strictfp任何地方使用它,并希望您不会遇到任何相关的错误
-但是在某些平台上,您会丢弃很多性能,而这些性能本来是可以让您双倍买下的。
没什么好说的,JVM规范将来不会再改变以适应FP硬件的进一步变化,或者JVM实现者不会使用默认的non-strictfp行为使他们做一些棘手的事情。
不精确的表示正如罗兰(Roland)在他的回答中指出的那样,一个关键问题
double是对于某些非整数值,它没有确切的表示形式。尽管
0.1在某些情况下(例如
Double.toString(0.1).equals("0.1")),单个非精确值通常会“往返”,但是一旦对这些不精确值进行数学运算,错误就会加重,并且这是无法恢复的。
特别是,如果您“接近”舍入点,例如〜1.005,则当真值为1.0050000001 …时,您可能会得到1.00499999 …的值,
反之亦然 。由于错误是双向的,因此没有可解决此问题的四舍五入方法。无法判断是否应将1.004999999
…的值调高。您的
roundToTwoPlaces()方法(一种双精度舍入)仅适用于以下情况,因为它处理了应将1.0049999增大的情况,但它永远无法越过边界,例如,如果累积错误导致1.0050000000001变成1.00499999999999,则无法修理它。
您不需要大数字或小数字即可达到目标。您只需要进行一些数学运算即可使结果接近边界。您做的数学越多,与真实结果的可能偏差越大,跨越边界的机会也就越大。
根据此处的要求,进行简单计算的搜索测试:将
amount*tax其四舍五入到小数点后两位(即美元和美分)。那里有一些舍入方法,当前使用的
roundToTwoPlacesB是您的1的增强版本(通过
n在第一舍入中增加乘数,您可以使其更加敏感-
原始版本立即在微不足道的输入上失败)。
该测试吐出了发现的故障,并且这些故障成束出现。例如,前几个失败:
Failed for 1234.57 * 0.5000 = 617.28 vs 617.29Raw result : 617.2850000000000000000000, Double.toString(): 617.29Failed for 1234.61 * 0.5000 = 617.30 vs 617.31Raw result : 617.3050000000000000000000, Double.toString(): 617.31Failed for 1234.65 * 0.5000 = 617.32 vs 617.33Raw result : 617.3250000000000000000000, Double.toString(): 617.33Failed for 1234.69 * 0.5000 = 617.34 vs 617.35Raw result : 617.3450000000000000000000, Double.toString(): 617.35
注意,“原始结果”(即确切的未取整结果)始终接近
x.xx5000边界。您的舍入方法在高端和低端都有错误。您无法以一般方式修复它。不精确的计算
几种
java.lang.Math方法不需要正确的取整结果,而是允许最大2.5ulp的误差。当然,您可能不会对货币使用过多的双曲线函数,但是诸如
exp()和这样的函数
pow()经常会在货币计算中找到用,它们的精度仅为1ulp。因此,返回数字时该数字已经“错误”。
这与“不精确表示”问题相关,因为这种类型的错误比正常的数学运算要严重得多,正常的数学运算至少要从的可表示域中选择最佳值
double。这意味着当您使用这些方法时,您可以有更多的圆边界穿越事件。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)