34

在 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 ?

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

4

2 回答 2

73

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);
        }
    }
}
于 2013-08-01T10:48:44.507 回答
2

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

计算机以及在其上运行的语言通常只实现有理数——隐藏在诸如 int、long int、float、double precision、real (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!

于 2013-08-06T23:48:06.220 回答