十进制vs再次加倍

时间:2017-02-21 16:56:41

标签: c# .net math

我支持财务.net应用程序。有很多建议可以使用十进制数据类型来获取财务资料。

现在我坚持这个:

decimal price = 1.0m/12.0m;
decimal quantity = 2637.18m;
decimal result = price * quantity;  //results in 219.76499999999999999999999991

问题是向我们的客户收取的正确价值是219.77(圆函数,MidpointRounding.AwayFromZero)而不是219.76。

如果我将所有内容都改为加倍,它似乎有效:

double price = 1.0/12.0;
double quantity = 2637.18;
double result = price * quantity;  //results in 219.765

我要把所有东西改成双倍吗?分数会有其他问题吗?

我认为这个问题与Difference between decimal, float and double in .NET?不同,因为它并没有真正向我解释为什么具有更精确数据类型 decimal 的结果不太准确(在上面的示例中)而不是使用较少字节的 double 数据类型的结果。

2 个答案:

答案 0 :(得分:4)

建议使用小数的原因是所有可以表示为非重复小数的数字都可以用十进制类型精确表示。现实世界中的货币单位总是不重复的小数。您的问题正如其他人所说的那样,由于某种原因,您的价格无法表示为非重复小数。那是0.083333333...。使用双精度实际上并没有帮助准确性 - 双精度也不能准确地代表1/12。在这种情况下,缺乏准确性不会导致问题,但在其他情况下可能会出现问题。

更重要的是,选择使用双精度意味着还有更多数字无法完全准确表示。例如0.01,0.02,0.03 ......是的,你可能关心的很多数字都不能准确地表示为双数。

在这种情况下,价格来自何处的问题确实是重要的问题。无论您在哪里存储该价格,几乎肯定不会存储1/12。您要么已存储近似值,要么该价格实际上是计算结果(或者您使用的是一个非常不寻常的数字存储系统,您可以存储有理数,但这似乎非常不可能)。

你真正想要的是一个可以表示为双倍的价格。如果这是你所拥有的,那么你修改它(例如,除以12得到每年的每月费用),那么你需要尽可能晚地进行这种划分。很可能你还需要计算每月成本作为未结余额的一个部分。我最后一部分的意思是,如果你每月支付10美元的分期付款,你可能会在第一个月收取0.83美元。然后第二个月你收费(10-0.83美元)/ 11。这将再次为0.83。在第五个月你收费(10-0.83 * 4)/ 8现在是0.84(一旦完成)。然后下个月它(10-0.83 * 4-0.84)/ 7等等。通过这种方式,您可以保证总费用是正确的,并且不用担心复合错误。

在一天结束时,您是唯一一个判断您是否可以重新设计系统以消除此类所有舍入错误或者是否必须按照我的建议以某种方式缓解它们的人。你最好的选择是阅读关于浮点数的所有内容,包括十进制和二进制数,这样你就可以完全理解选择一个在另一个上的含义。

答案 1 :(得分:1)

通常,在财务计算中,乘法和除法预计会以某种方式四舍五入到一定数量的小数位。 (大多数货币系统只使用基数为10的金额;在这些系统中,非基数10的金额很少见,如果它们发生的话。)将价格除以12并不总是预计会产生基数10号;业务逻辑将决定如何舍入该价格,包括结果将具有的小数位数。根据业务逻辑,0.083333333333333333这样的结果可能不合适。