2013-08-01 148 views
34

我注意到一个奇怪的行为,当乘以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 ? 

我无法理解的是以上两种情况。这怎么可能?

+11

欢迎来到精确错误的美妙世界。 – christopher

+0

这不是精确错误,它只是四舍五入。 –

+0

这是你的一个数学难题:'10/9 = 1.111 ...'; '1.111 ... * 9 = 9.999 ...≠10'。 ;) – stakx

回答

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); 
     } 
    } 
} 
+0

我不确定你的意思是“依靠”28位数字。你的意思是明确地将诸如分裂结果之类的东西四舍五入吗? “Decimal”使用基数为10的指数而不是基数为2的事实意味着“Decimal”的名义数值将与其简洁的字符串表示相匹配,但是它是浮点类型的事实表明等式用'Decimal'测试可能会遇到与任何其他浮点类型相同的问题。 – supercat

+0

@supercat:我的意思是,如果你有29位数字需要表示,你可能无法完全做到,但你可以完全代表28位数字。我认为依赖于'decimal'的平等比'float'/'double'更合理 - 一方面,您不必担心某些操作可能更准确地执行,这取决于价值在寄存器中或不在。它仍然必须小心谨慎地做,但在很多情况下,我认为它会好起来的。 –

2

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

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

微不足道的示例 - 未在计算机上找到。 1/2 * 1/2 = 1/4如果您有一个有理数类,并且分子和分母的大小不超过整数算术的限制,那么这很有效。所以(1,2)*(1,2) - >(1,4)

但是,如果可用的有理数是十进制并限于小数点后的一位数 - 不切实际 - 但代表选择时选择一个用于近似有理数(float/real等)数值的实现,那么1/2将完全转换为0.5,那么0.5 + 0.5将等于1.0,但0.5 * 0.5将不得不为0.2或0.3!