【问题标题】:Is C# Decimal Rounding Inconsistent?C# 十进制舍入不一致?
【发布时间】:2012-04-13 11:12:29
【问题描述】:

我一直在与来自 SQL 小数 (38,30) 的 C# 中的小数精度作斗争,我终于把它弄到了一个四舍五入的怪圈。我知道我可能在这里忽略了显而易见的事情,但我需要一点洞察力。

我遇到的问题是 C# 不能产生我认为是一致的输出。

decimal a = 0.387518769125m;
decimal b = 0.3875187691250002636113061835m;

Console.WriteLine(Math.Round(a, 11));
Console.WriteLine(Math.Round(b, 11));
Console.WriteLine(Math.Round(a, 11) == Math.Round(b, 11));

产量

0.38751876912
0.38751876913
False

呃,0.38751876913?真的吗?我在这里错过了什么?

来自MSDN

如果小数位为奇数,则改为偶数。否则,保持不变。

为什么我看到不一致的结果?额外的精度不会改变“小数位中的数字”...

【问题讨论】:

  • 它向下取整一个值 0.5。 (即“0.50000”向下取整,“0.50002...”向上取整) - 这不是您所期望的吗?见msdn.microsoft.com/en-us/library/ms131274.aspx
  • 您希望 round 函数四舍五入到 更远的数字?你能解释一下为什么在任何情况下你会期望这种情况吗? (这不是一个修辞问题;我有兴趣了解为什么人们相信非常奇怪的事情。这个问题实际上很常见,但我仍然不明白为什么这么多人认为 round 应该四舍五入更远的数字。)
  • @EricLippert 我认为常见的误解源于小学数学课通过查看下一个数字来教授四舍五入的事实。如果那个数字是 5,我们会学习四舍五入。然后我们了解银行家的四舍五入,许多人错误地假设“四舍五入到最接近的偶数”适用于下一个数字是 5。当然,它仅适用于下一个数字是5 并且在那之后没有更多的非零数字。即使是 Oded 引用的文档也具有误导性:如果您严格阅读它,1.0005 中的单个非零 5 数字将导致2的返回值。
  • 我想我的问题与对 MSDN 文档的误解有关。它似乎说明“如果数字在小数位”。对我来说,这意味着“我们只看那个数字来做出决定”。你说的很有道理,但我只是在文档中看不到这一点。即使再看一遍。我会重新阅读该文档,看看您的反馈是否更清楚。
  • 但是“四舍五入”规则是一个平局规则; 当已经有明确的赢家时,您不会应用平局规则。

标签: c# rounding


【解决方案1】:

来自 MSDN:

如果decimals小数点右侧有一个非零数字decimals其值为5,则该数字如果是奇数,则小数位向上舍入,或如果是偶数则保持不变。如果d 的小数位数少于decimals,则d 原样返回。

第一种情况

decimal a = 0.387518769125m;
Console.WriteLine(Math.Round(a, 11));

第 11 位右边有一个数字那个数字是5。因此,因为位置 11 是偶数,所以保持不变。因此,你得到

0.38751876912

第二种情况

decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(b, 11));

第 11 位的右侧没有一位数。因此,这是直接向上的小学四舍五入;如果下一个数字大于 4,则四舍五入,否则向下舍入。由于第 11 位右边的数字大于 4(它是 5),所以我们四舍五入让您看到

0.38751876913

为什么我看到不一致的结果?

你不是。结果与文档完全一致。

【讨论】:

  • 只是为了确保我理解这里,ToEven 或 AwayFromZero 作为默认值会根据数字的精度而变化?
  • 我喜欢您的回答,我认为它显示了 MSDN 文档中缺少的内容。也就是说,如果它不是单个非零数字会发生什么。
  • @BennettDill “单个非零数字等于 5”是一种令人困惑的说法,即“方法的参数正好在两个可能的返回值之间的中间”。如果在指定位置的右侧没有单个数字,或者如果只有一个数字不是 5,则参数不完全位于可能的返回值之间,因此您选择返回更接近参数的值。啊,如果我们只使用以 3 为底的数字,我们就不用担心这个了!
  • @ScottChamberlain:嗯,我认为 Math.Round 默认为 ToEven(银行家的四舍五入)。您必须指定您想要的 AwayFromZero(语义算术舍入),这在此处没有发生。
  • 这意味着如果您选择 AwayFromZero 舍入,这两个数字将舍入到相同的 11 位值,因为您提到的规则是银行家舍入算法的一部分。
【解决方案2】:

来自 MSDN - Math.Round Method (Decimal, Int32):

如果 d 中小数点右侧有一个非零数字且其值为 5,则小数点位置的数字如果是奇数则四舍五入,如果是偶数则保持不变。如果 d 的小数位数少于小数,则 d 原样返回。

此方法的行为遵循 IEEE 标准 754 第 4 节。这种四舍五入有时称为四舍五入或银行家四舍五入。它可以最大限度地减少由于在单个方向上持续舍入中点值而导致的舍入误差。

注意单个非零数字的使用。这对应于您的第一个示例,但不是第二个。

还有:

要控制 Round(Decimal, Int32) 方法使用的舍入类型,请调用 Decimal.Round(Decimal, Int32, MidpointRounding) 重载。

【讨论】:

  • 我认为我的问题不在于使用了哪种类型的舍入。我相信它不理解 MidpointRounding.ToEven 本身的行为。 :-( 这个问题的答案和cmets现在已经清楚地说明了,谢谢大家!
【解决方案3】:

“d 中小数点右侧的单个非零数字,其值为 5”部分解释了结果。只有当要舍入的部分正好是 0,5 时,舍入规则才起作用。

【讨论】:

  • 从 MSDN 的文档中我不清楚这一点。不过感谢您的解释。
【解决方案4】:

让我们将两个数字向左移动 11 位以上:

38751876912.5
38751876912.50002636113061835

使用银行家四舍五入,我们将第一个向下舍入。在每个中点舍入系统下,我们将第二个数字向上舍入(因为它不在中点处)

.Net 正在做我们期望的事情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多