Why is floating point arithmetic in C# imprecise?
为什么下面的程序打印它打印的内容?
1 2 3 4 5 6 7 8 9 10 | class Program { static void Main(string[] args) { float f1 = 0.09f*100f; float f2 = 0.09f*99.999999f; Console.WriteLine(f1 > f2); } } |
输出是
1 | false |
浮点的精度只有这么多位数。如果您看到f1==f2,这是因为任何差异都需要比32位浮点所能表示的精度更高的精度。
我建议每个计算机科学家都应该阅读关于浮点的内容
主要的事情是,这不仅仅是.NET:它是底层系统的一个限制,大多数语言都将使用它来表示内存中的浮点。精确到目前为止。
当你考虑到它甚至不是以10为基数时,你也可以用相对简单的数字来享受一些乐趣。例如,用二进制表示时,0.1是一个重复的十进制数。
在这种特殊情况下,这是因为.09和.999999不能用二进制精确表示(同样,1/3不能用十进制精确表示)。例如,0.111111111111111101111基2是0.99998986721038818359375基10。前一个二进制值加1,0.11111111111111111的基2是0.9999994632568359375的基10。没有精确为0.999999的二进制值。浮点精度也受到分配用于存储指数和尾数小数部分的空间的限制。此外,与整数类型一样,浮点也可以溢出其范围,尽管其范围大于整数范围。
在XCODE调试器中运行此位C++代码,
float myfloat=0.1;
显示myfloat的值为0.10000000001。关闭0.00000001。不是很多,但是如果计算有几个算术运算,不精确性就可以复合起来。
imho关于浮点的一个很好的解释,请参阅加利福尼亚州立大学Sonoma(退休)的Bob Plantz对x86-64汇编语言和gnu/linux的介绍第14章http://bob.cs.sonoma.edu/getting_book.html。以下是基于这一章。
浮点类似于科学记数法,其中一个值存储为大于或等于1.0小于2.0(尾数)的混合数,乘以另一个数的某个幂(指数)。浮点使用的是基数2而不是基数10,但在简单的模型plantz中,为了清晰起见,他使用的是基数10。假设一个系统,尾数使用两个存储位置,一个位置用于指数*的符号(0表示+和1表示-),一个位置用于指数。现在添加0.93和0.91。答案是1.8,而不是1.84。
9311表示0.93,或9.3乘以10到-1。
9111表示0.91,或9.1乘以10到-1。
准确的答案是1.84,即1.84乘以10到0,如果我们有5个位置,这将是18400,但是,只有4个位置,答案是1800,或者1.8乘以10到0,或者1.8。当然,浮点数据类型可以使用四个以上的存储位置,但是位置的数量仍然有限。
不仅精度受空间限制,而且"二进制小数的精确表示仅限于两个逆幂的和"(plantz,op.cit)。
0.11100110(二进制)=0.89843750(十进制)
0.11100111(二进制)=0.90234375(十进制)
二进制中没有0.9十进制的精确表示。即使把分数带到更多的地方也不行,因为你要在右边永远重复1100次。
Beginning programmers often see floating point arithmetic as more
accurate than integer. It is true that even adding two very large
integers can cause overflow. Multiplication makes it even more likely
that the result will be very large and, thus, overflow. And when used
with two integers, the / operator in C/C++ causes the fractional part
to be lost. However, ... floating point representations have their own
set of inaccuracies. (Plantz, op. cit.)
*在浮点中,数字的符号和指数的符号都被表示出来。