奇怪的C#小数乘法行为

我注意到在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 ? 

我不明白的是以上两种情况。 这怎么可能?

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

这意味着高达大约7.9 ….你可以准确地得到29位有效数字 – 但以上,你不能。 这就是为什么8和9都出错了,而不是先前的值。 一般只能依靠 28个有效数字,以避免这种奇怪的情况。

一旦你减less你的原始input到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); } } } 

math家区分有理数和超集实数。 有理数的算术运算是精确定义的。 对实数进行算术运算(使用加法,减法,乘法和除法运算符)是“精确”的,只是无理数在无理数forms(符号)下或在某些expression式中可能转换为有理数。 例如,二的平方根没有小数(或任何其他有理基数)表示。 但是,乘以二的平方根的两个平方根显然是有理的-2。

计算机及其上运行的语言通常只实现有理数 – 隐藏在诸如int,long int,float,double precision,real(FORTRAN)之类的名称后面,或者暗示其他名称的实数。 但是包含的有理数是有限的,不同于范围是无穷的有理数。

微不足道的例子 – 在电脑上找不到。 1/2 * 1/2 = 1/4如果你有一个有理数的类,那么分子和分母的大小不会超过整数运算的限制。 所以(1,2)*(1,2) – >(1,4)

但是,如果可用的有理数是十进制的,并且在十进制之后被限制为一个数字 – 这是不切实际的 – 但是代表select用于近似有理数(浮点数/实数等)的实现时的select,则1/2将是完美的可转换为0.5,那么0.5 + 0.5将等于1.0,但0.5 * 0.5将不得不是0.2或0.3!