maa*_*nus 44 java floating-point currency biginteger
我们知道使用double货币是容易出错的,不推荐使用.但是,我还没有看到一个现实的例子,BigDecimal虽然double失败了,但不能通过一些舍入来简单地修复.
注意琐碎的问题
double total = 0.0;
for (int i = 0; i < 10; i++) total += 0.1;
for (int i = 0; i < 10; i++) total -= 0.1;
assertTrue(total == 0.0);
Run Code Online (Sandbox Code Playgroud)
不计算,因为它们通过舍入(在这个例子中从0到16的小数位的任何东西)都可以解决.
涉及总结大值计算可能需要一些中间圆棒,但由于流通总货币之中USD 1e12,爪哇double(即标准IEEE双精度其15个十进制数字)仍然是足够美分事件.
涉及分工的计算通常是不精确的,即使是BigDecimal.我可以构造一个不能用doubles 执行的计算,但可以BigDecimal使用100的标度执行,但它不是你在现实中可以遇到的东西.
我没有声称这样一个现实的例子不存在,只是我还没有看到它.
我也肯定同意,使用double更容易出错.
我正在寻找的是如下方法(根据Roland Illig的回答)
/**
* Given an input which has three decimal places,
* round it to two decimal places using HALF_EVEN.
*/
BigDecimal roundToTwoPlaces(BigDecimal n) {
// To make sure, that the input has three decimal places.
checkArgument(n.scale() == 3);
return n.round(new MathContext(2, RoundingMode.HALF_EVEN));
}
Run Code Online (Sandbox Code Playgroud)
和一个像测试一样
public void testRoundToTwoPlaces() {
final BigDecimal n = new BigDecimal("0.615");
final BigDecimal expected = new BigDecimal("0.62");
final BigDecimal actual = roundToTwoPlaces(n);
Assert.assertEquals(expected, actual);
}
Run Code Online (Sandbox Code Playgroud)
当使用时被天真地重写时double,测试可能会失败(它不是针对给定的输入,而是针对其他输入).但是,它可以正确完成:
static double roundToTwoPlaces(double n) {
final long m = Math.round(1000.0 * n);
final double x = 0.1 * m;
final long r = (long) Math.rint(x);
return r / 100.0;
}
Run Code Online (Sandbox Code Playgroud)
它很丑陋且容易出错(并且可能会简化),但它可以很容易地封装在某个地方.这就是为什么我在寻找更多的答案.
Bee*_*ope 27
double在处理货币计算时,我可以看到四种可能会让你感到困惑的基本方法.
尾数中精度约为15位十进制数,只要你处理的金额大于此值,你就会得到错误的结果.如果你跟踪美分,问题将在10 13(10万亿美元)之前开始出现.
虽然这是一个很大的数字,但并不是那么大.美国的国内生产总值约为18万亿美元,超过它,所以处理国家甚至公司规模的金额都很容易得到错误的答案.
此外,有很多方法可以在计算过程中将更小的数量超过此阈值.您可能正在进行增长预测或多年,这会产生较大的最终价值.您可能正在进行"假设"情景分析,其中检查各种可能的参数,并且某些参数组合可能导致非常大的值.您可能会根据财务规则进行工作,这些规则允许分数可以从您的范围中再削减两个数量级或更多,这使您大致与美元中的个人财富一致.
最后,我们不要以美国为中心的观点.其他货币怎么样?印尼盾的价值约为 13,000美元,因此您需要跟踪该货币的货币金额另外2个数量级(假设没有"美分"!).你几乎要达到凡人所感兴趣的金额.
这是一个例子,从1e9开始,5%的增长预测计算错误:
method year amount delta
double 0 $ 1,000,000,000.00
Decimal 0 $ 1,000,000,000.00 (0.0000000000)
double 10 $ 1,628,894,626.78
Decimal 10 $ 1,628,894,626.78 (0.0000004768)
double 20 $ 2,653,297,705.14
Decimal 20 $ 2,653,297,705.14 (0.0000023842)
double 30 $ 4,321,942,375.15
Decimal 30 $ 4,321,942,375.15 (0.0000057220)
double 40 $ 7,039,988,712.12
Decimal 40 $ 7,039,988,712.12 (0.0000123978)
double 50 $ 11,467,399,785.75
Decimal 50 $ 11,467,399,785.75 (0.0000247955)
double 60 $ 18,679,185,894.12
Decimal 60 $ 18,679,185,894.12 (0.0000534058)
double 70 $ 30,426,425,535.51
Decimal 70 $ 30,426,425,535.51 (0.0000915527)
double 80 $ 49,561,441,066.84
Decimal 80 $ 49,561,441,066.84 (0.0001678467)
double 90 $ 80,730,365,049.13
Decimal 90 $ 80,730,365,049.13 (0.0003051758)
double 100 $ 131,501,257,846.30
Decimal 100 $ 131,501,257,846.30 (0.0005645752)
double 110 $ 214,201,692,320.32
Decimal 110 $ 214,201,692,320.32 (0.0010375977)
double 120 $ 348,911,985,667.20
Decimal 120 $ 348,911,985,667.20 (0.0017700195)
double 130 $ 568,340,858,671.56
Decimal 130 $ 568,340,858,671.55 (0.0030517578)
double 140 $ 925,767,370,868.17
Decimal 140 $ 925,767,370,868.17 (0.0053710938)
double 150 $ 1,507,977,496,053.05
Decimal 150 $ 1,507,977,496,053.04 (0.0097656250)
double 160 $ 2,456,336,440,622.11
Decimal 160 $ 2,456,336,440,622.10 (0.0166015625)
double 170 $ 4,001,113,229,686.99
Decimal 170 $ 4,001,113,229,686.96 (0.0288085938)
double 180 $ 6,517,391,840,965.27
Decimal 180 $ 6,517,391,840,965.22 (0.0498046875)
double 190 $ 10,616,144,550,351.47
Decimal 190 $ 10,616,144,550,351.38 (0.0859375000)
Run Code Online (Sandbox Code Playgroud)
三角洲(160年之间的差异double和BigDecimal首次命中率相差> 1美分,大约2万亿(从现在开始160年可能不会那么多),当然只会变得更糟.
当然,53位的Mantissa意味着这种计算的相对误差可能非常小(希望你的工作不会超过2万亿美元).实际上,在大多数示例中,相对误差基本上保持稳定.你当然可以组织它,以便你(例如)在尾数中减去两个不同的精度,导致一个任意大的错误(练习到读者).
所以你认为你非常聪明,并设法提出一个舍入方案,让你可以double在本地JVM上使用并详尽测试你的方法.继续部署它.明天或下周,或者每当你最糟糕的时候,结果都会改变,你的技巧也会破裂.
与几乎所有其他基本语言表达式不同,当然与整数或BigDecimal算术不同,默认情况下,由于strictfp功能,许多浮点表达式的结果没有单个标准定义值.平台可以自由选择使用更高精度的中间体,这可能会导致不同硬件,JVM版本等产生不同的结果.对于相同的输入,当方法从解释转换为JIT时,结果甚至可能会在运行时发生变化 -compiled!
如果您在Java之前的1.2天编写了代码,那么当Java 1.2突然引入now-default变量FP行为时,您会非常生气.你可能只想strictfp在任何地方使用它,并希望你不要遇到任何相关的错误 - 但在某些平台上,你会丢掉大部分双重购买你的性能.
没有什么可说的,JVM规范将来不会再次改变以适应FP硬件的进一步变化,或者JVM实现者不会使用默认的非strictfp行为使他们做一些棘手的事情.
正如Roland在他的回答中所指出的,一个关键问题double是它没有对某些大多数非整数值的精确表示.虽然0.1在某些情况下(例如Double.toString(0.1).equals("0.1")),单个非精确值通常会"往返" ,但只要对这些不精确的值进行数学计算,错误就会复合,这可能是不可恢复的.
特别是,如果您"接近"舍入点,例如~1.005,则可能得到值1.00499999 ...当真值为1.0050000001时,反之亦然.因为错误是双向的,所以没有可以解决这个问题的舍入魔法.没有办法判断是否应该增加1.004999999 ......的值.你的roundToTwoPlaces()方法(一种双舍入)只能起作用,因为它处理的情况是1.0049999应该被提升,但它永远不能越过边界,例如,如果累积误差导致1.0050000000001变成1.00499999999999它不能修理它.
你不需要大或小的数字来达到这个目的.你只需要一些数学,结果就会接近边界.你做的数学越多,与真实结果的可能偏差越大,跨越边界的可能性就越大.
按此处要求进行简单计算的搜索测试:amount * tax并将其舍入为2位小数(即美元和美分).有一些舍入方法,目前使用的roundToTwoPlacesB是你的1的加强版本(通过增加n第一轮的乘数,你使它更敏感 - 原始版本立即失败的琐碎输入).
测试吐出它发现的失败,它们串成一团.例如,前几个失败:
Failed for 1234.57 * 0.5000 = 617.28 vs 617.29
Raw result : 617.2850000000000000000000, Double.toString(): 617.29
Failed for 1234.61 * 0.5000 = 617.30 vs 617.31
Raw result : 617.3050000000000000000000, Double.toString(): 617.31
Failed for 1234.65 * 0.5000 = 617.32 vs 617.33
Raw result : 617.3250000000000000000000, Double.toString(): 617.33
Failed for 1234.69 * 0.5000 = 617.34 vs 617.35
Raw result : 617.3450000000000000000000, Double.toString(): 617.35
Run Code Online (Sandbox Code Playgroud)
请注意,"原始结果"(即精确的未结果结果)始终接近x.xx5000边界.你的舍入方法在高低两侧都会出错.你无法解决这个问题.
有些java.lang.Math方法不需要正确的舍入结果,而是允许高达2.5 ulp的错误.当然,你可能都不会被使用双曲线函数与多币种,但功能,如exp()和pow()经常发现他们的方式进入货币计算,这些只有1个ULP的精确度.因此,返回时该数字已经"错误".
这与"不精确表示"问题相互作用,因为这种类型的错误比正常数学运算严重得多,正常数学运算至少从可表示域中选择最佳可能值double.这意味着当您使用这些方法时,您可以拥有更多的圆边界交叉事件.
Rol*_*lig 20
当你四舍五入double price = 0.615到两位小数时,得到0.61(向下舍入),但可能预期为0.62(因为5而被四舍五入).
这是因为双0.615实际上是0.6149999999999999911182158029987476766109466552734375.
Hen*_*nry 11
您在实践中遇到的主要问题与round(a) + round(b)不一定相等的事实有关round(a+b).通过使用BigDecimal您可以很好地控制舍入过程,因此可以使您的总和正确.
当您计算税收时,例如18%的增值税,当准确表示时,很容易获得具有两个以上小数位的值.四舍五入成为一个问题.
让我们假设你买了2篇文章,每篇1.3美元
Article Price Price+VAT (exact) Price+VAT (rounded)
A 1.3 1.534 1.53
B 1.3 1.534 1.53
sum 2.6 3.068 3.06
exact rounded 3.07
Run Code Online (Sandbox Code Playgroud)
因此,如果您使用double进行计算并且仅使用round来打印结果,那么您将获得总计3.07,而账单上的金额实际上应该是3.06.
让我们在这里给出一个"技术性较低,更具哲学性"的答案:为什么你认为"Cobol"在处理货币时不使用浮点运算?!
(引用中的"Cobol",如:解决现实世界业务问题的现有遗留方法).
含义:差不多50年前,当人们开始使用计算机进行业务而非金融工作时,他们很快意识到"浮点"表示对金融行业不起作用(可能期望在问题中指出一些罕见的利基角落) ).
请记住:那时候,抽象真的很贵!它的价格足够昂贵,并且在那里有一个寄存器; 我们站在他们的肩膀上的巨人们很快就会明白......使用"浮点"并不能解决他们的问题; 并且他们不得不依赖别的东西; 更抽象 - 更贵!
我们的行业有50多年的时间来提出"适用于货币的浮动点" - 而且常见的答案仍然是:不要这样做.相反,你转向BigDecimal等解决方案.
你不需要一个例子.你只需要第四种形式的数学.浮点中的分数以二进制基数表示,二进制基数与十进制基数不可比较.十年级的东西.
因此,总会有舍入和近似,并且在任何方式,形状或形式的会计中都不可接受.这些书必须平衡到最后一分钱,因此,FYI在每天结束时和整个银行定期进行银行分行.
遭受四舍五入错误的表达不计算在内
荒谬.这是问题所在.排除舍入误差排除了整个问题.
| 归档时间: |
|
| 查看次数: |
6806 次 |
| 最近记录: |