【问题标题】:C# decimal multiplication strange behaviorC# 十进制乘法奇怪的行为
【发布时间】:2013-08-02 05:46:19
【问题描述】:

在 C# 中乘以十进制值时,我注意到一个奇怪的行为。考虑以下乘法运算:

1.1111111111111111111111111111m * 1m = 1.1111111111111111111111111111 // OK
1.1111111111111111111111111111m * 2m = 2.2222222222222222222222222222 // OK
1.1111111111111111111111111111m * 3m = 3.3333333333333333333333333333 // OK
1.1111111111111111111111111111m * 4m = 4.4444444444444444444444444444 // OK
1.1111111111111111111111111111m * 5m = 5.5555555555555555555555555555 // OK
1.1111111111111111111111111111m * 6m = 6.6666666666666666666666666666 // OK
1.1111111111111111111111111111m * 7m = 7.7777777777777777777777777777 // OK
1.1111111111111111111111111111m * 8m = 8.888888888888888888888888889  // Why not 8.8888888888888888888888888888 ?
1.1111111111111111111111111111m * 9m = 10.000000000000000000000000000 // Why not 9.9999999999999999999999999999 ?

我无法理解的是上述最后两个案例。这怎么可能?

【问题讨论】:

  • 欢迎来到精确误差的奇妙世界。
  • 这不是精度错误,只是四舍五入。
  • 这是一个数学难题:10 / 9 = 1.111…; 1.111… * 9 = 9.999… ≠ 10。 ;)

标签: c# decimal multiplication


【解决方案1】:

decimal 存储 28 或 29 个有效数字(96 位)。基本上尾数在 -/+ 79,228,162,514,264,337,593,543,950,335 范围内。

这意味着最多约 7.9.... 您可以准确地获得 29 位有效数字 - 但高于此您不能。这就是为什么 8 和 9 都出错了,而不是早期的值。通常,您应该依靠 28 位有效数字,以避免出现这种奇怪的情况。

将原始输入减少到 28 位有效数字后,您将获得预期的输出:

using System;

class Test
{
    static void Main()
    {
        var input = 1.111111111111111111111111111m;
        for (int i = 1; i < 10; i++)
        {
            decimal output = input * (decimal) i;
            Console.WriteLine(output);
        }
    }
}

【讨论】:

  • 我不确定您所说的“依赖”28 位数字到底是什么意思。你的意思是明确地四舍五入诸如除法结果之类的东西吗? Decimal 使用以十为底的指数而不是以二为底的事实意味着 Decimal 的标称数值将匹配其简洁的字符串表示形式,但它是浮点类型的事实表明相等- 使用Decimal 进行测试很容易遇到与任何其他浮点类型相同的问题。
  • @supercat:我的意思是,如果您需要表示 29 位数字,您可能无法准确地做到这一点,但您最多可以准确地表示 28 位数字。我认为依赖 decimal 的相等性比 float/double 更合理 - 一方面,您不必担心某些操作是否会更准确地执行,具体取决于是否值是否在寄存器中。它仍然需要非常小心地完成,但在许多情况下我认为它会很好。
【解决方案2】:

数学家区分有理数和超集实数。有理数的算术运算定义明确且精确。对实数的算术(使用加法、减法、乘法和除法运算符)只有在无理数以无理数形式(符号)或可能在某些表达式中可转换为有理数的情况下才是“精确的” .例如,二的平方根没有十进制(或任何其他有理基数)表示。然而,二的平方根乘以二的平方根显然是有理数 - 2。

计算机及其上运行的语言通常只实现有理数 - 隐藏在诸如 int、long int、float、双精度、实数 (FORTRAN) 或其他暗示实数的名称后面。但所包含的有理数是有限的,不像有理数集的范围是无限的。

简单示例 - 在计算机上找不到。 1/2 * 1/2 = 1/4 如果你有一类有理数并且分子和分母的大小不超过整数算术的限制,那就可以了。所以 (1,2) * (1,2) -> (1,4)

但是,如果可用的有理数是十进制数并且限制为小数点后的一位数 - 不切实际 - 但代表了在选择近似有理数(浮点数/实数等)的实现时所做的选择,那么 1/2可以完全转换为 0.5,然后 0.5 + 0.5 将等于 1.0,但 0.5 * 0.5 必须是 0.2 或 0.3!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-14
    • 1970-01-01
    • 1970-01-01
    • 2012-01-22
    • 2014-02-12
    相关资源
    最近更新 更多