【问题标题】:decimal vs double again小数与双倍
【发布时间】:2017-02-21 16:56:41
【问题描述】:

我支持金融 .net 应用程序。有很多建议将 decimal 数据类型用于财务数据。

现在我被这个困住了:

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 数据类型。

【问题讨论】:

  • 只是计算中的一个基本缺陷,除非你用 12 个手指数数,否则 1/12 永远不会有完美的结果。什么样的公司会向他们的客户收取 12 美元的商品费用?您必须先确定正确的单价。
  • “我应该把所有东西都改成双倍吗?” - 为什么?您发现了一个未满足您的期望的示例(尽管正如 Hans 指出的那样,您有一个奇怪的起点,即您的单位价格不容易表示,然后我要指出这不是单位价格,因为您显然可以细分您的单位)。最好为您的数量选择更好的表示。很有可能,有可能找到相同和相反的例子,其中decimal 适合并且double 产生错误的结果。
  • 把除法放在最后:1m*2637.18m/12m 而不是1m/12m*2637.18m
  • @PetSerAI:将除法放在最后确实会改变结果:219.765M。 Tnx 为那个。

标签: c# .net math


【解决方案1】:

推荐使用小数的原因是所有可以表示为不重复小数的数字都可以准确地表示为小数类型。现实世界中的货币单位总是不重复的小数。正如其他人所说,您的问题是您的价格由于某种原因不能表示为非重复小数。那就是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 等等。这样您就可以保证总费用是正确的,而不必担心复合错误。

在一天结束的时候,你是唯一一个判断你是否可以重新设计你的系统以消除所有像这样的舍入错误,或者你是否必须按照我的建议以某种方式减轻它们。不过,最好的办法是阅读有关浮点数的所有信息,包括十进制和二进制,以便您完全理解选择一个而不是另一个的含义。

【讨论】:

    【解决方案2】:

    通常,在财务计算中,乘法和除法应以特定方式四舍五入到一定的小数位。 (大多数货币系统仅使用以 10 为基数的货币;在这些系统中,即使发生过非以 10 为基数的货币也很少见。)将价格除以 12 并不总是期望得到基数10个号码;业务逻辑将决定该价格将如何四舍五入,包括结果的小数位数。根据业务逻辑,0.083333333333333333 这样的结果可能不合适。

    【讨论】:

      猜你喜欢
      • 2014-02-28
      • 1970-01-01
      • 2012-10-31
      • 2014-03-02
      • 1970-01-01
      • 1970-01-01
      • 2013-05-07
      • 2011-04-10
      • 1970-01-01
      相关资源
      最近更新 更多