为什么C#中的浮点数算术不精确?
为什么C#中的浮点运算是不精确的?
在这种特殊情况下,是因为0.09和0.999999无法以二进制精确表示(类似地,1/3无法以十进制精确表示)。例如,0.111111111111111111101111二进制表示为0.999998986721038818359375十进制。将1添加到前一个二进制值,0.11111111111111111111二进制表示为0.99999904632568359375十进制。没有一个二进制值可以精确表示0.999999。浮点精度还受到存储指数和尾数的分数部分的空间限制。与整数类型一样,浮点也可以超出其范围,尽管其范围比整数范围更大。
在Xcode调试器中运行以下C ++代码:
float myFloat = 0.1;
可以看到myFloat的值为0.100000001。它偏离了0.000000001。虽然不多,但如果计算有多个算术操作,不精确性可能会被累积。
浮点数的一个很好的解释在加利福尼亚州索诺玛州立大学(退休)的Bob Plantz的《Introduction to Computer Organization with x86-64 Assembly Language & 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,但是由于只有四个位置,答案是1800,即1.8乘以10的零次方,即1.8。当然,浮点数据类型可以使用多个存储位置,但是位置的数量仍然有限。
精度不仅受到空间的限制,而且“在二进制中,对分数值的精确表示仅限于二的倒数之和。”(Plantz,op. cit.)。
0.11100110(二进制)= 0.89843750(十进制)
0.11100111(二进制)= 0.90234375(十进制)
在二进制中没有0.9十进制的精确表示。即使将小数部分延长到更多位,也无法解决这个问题,因为右侧会无限重复1100。
初学者通常认为浮点运算比整数运算更准确。确实,即使将两个非常大的整数相加也可能导致溢出。乘法使结果更可能非常大,从而溢出。而在C / C ++中,当与两个整数一起使用时,/运算符会丢失小数部分。但是,浮点表示也有其自身的不准确之处(Plantz,op. cit.)。
*在浮点数中,数的符号和指数的符号都有所表示。
浮点数在C#中不精确的原因是什么?
浮点数在C#中不精确是由于底层系统的限制所导致的,这个限制也适用于大多数编程语言中用于表示浮点数的方式。浮点数的精度有限。
即使是相对简单的数字也可能会出现问题,因为浮点数不是基于十进制表示的。例如,0.1(1/10)在二进制表示中是一个重复的十进制数,就像1/3在十进制表示中一样是一个重复的十进制数。
解决方法:
1. 使用decimal类型代替float或double类型。decimal类型提供了更高的精度,适用于需要更精确计算的场景。
2. 避免直接比较浮点数的相等性。由于浮点数的精度问题,直接比较两个浮点数的值可能会出现错误的结果。可以使用误差范围或使用Decimal.Equals方法来比较浮点数的相等性。
3. 尽量避免在循环中进行浮点数运算。由于浮点数的精度问题,多次进行浮点数运算可能会导致累积误差。可以考虑使用整数运算或使用BigDecimal类进行精确计算。
4. 了解浮点数的特性和限制。了解浮点数在二进制表示中的特点,以及由于精度限制可能导致的误差,有助于更好地理解和处理浮点数计算中的问题。
浮点数在C#中不精确是由于底层系统的限制所导致的。为了解决这个问题,可以使用decimal类型代替float或double类型,避免直接比较浮点数的相等性,避免在循环中进行浮点数运算,并了解浮点数的特性和限制。