为什么 [float.MaxValue == float.MaxValue + 1] 返回 true?

29

我想知道你能否解释一下浮点类型中的溢出(Overflow)。

float.MaxValue == float.MaxValue + 1 // returns true

1
我猜这和无限加一仍然是无限的原因相同吧。 - Blindy
14
@Blindy:不是。更像是海啸中的泪水。 - Shog9
我认为有必要指出(因为到目前为止我还没有在任何答案中看到)浮点数(或双精度数或十进制数)的精度取决于值本身,“7位有效数字”只是相对较小值的启发式。例如,浮点数在接近0时非常精确。另一方面,双精度数(如IEEE-754,64位)只能表示所有整数到约2^53,这远远小于它的整个范围。 - user166390
1
@pst:这是7个有效数字。相对精度。绝对精度如你所说,取决于值(特别是指数),这是科学计数法的一般规律。 - Ben Voigt
@Ben Voight,那是我用词不当:“小数点后7位数字”,虽然严格来说并不准确,但这就是我应该写的内容。这是为了强调为什么需要一个“大于1”的数字才能使更大的浮点值发生变化。 - user166390
5个回答

24

由于数字 1 太小了,无法对 float.MaxValue 的值产生影响。

任何小于 1e32 的数将低于浮点数的精度,因此实际上相当于添加了一个零。

编辑:

ulrichb 表明值为 1e23 实际上确实会影响 float.MaxValue,这必须意味着你并没有比较浮点数,而是双精度浮点数。编译器在添加和比较之前将所有值转换为双精度浮点数。


6
如果你非常快且频繁地加上1,会发生什么呢? :) - kenny
@kenny和Blindy:即使极其频繁地添加非常大的1,仍然无法达到1e32。你可能最多只能达到约1e12。 :) - Guffa
4
你可能需要更好的1。 - James Anderson

9

那非常有趣:

float fMax = float.MaxValue;
double dMax = double.MaxValue;

Console.WriteLine("{0}, {1}", fMax == fMax + 1E22f, fMax + 1E22f);
Console.WriteLine("{0}, {1}", fMax == fMax + 1E23f, fMax + 1E23f);

Console.WriteLine("{0}, {1}", dMax == dMax + 1E291d, dMax + 1E291d);
Console.WriteLine("{0}, {1}", dMax == dMax + 1E292d, dMax + 1E292d);

输出:

真, 3.402823E+38
假, 3.402823E+38
真, 1.79769313486232E+308
假, 无穷大

因此,正如Guffa所指出的那样,fMax + 1E23f 被转换为双精度浮点数,并且 dMax + 1E292d 相加结果为 Infinity


有趣的是... 结果具有比float能够容纳的更多有效数字,因此结论必须是编译器在执行加法和比较之前将所有float值转换为double。 - Guffa
@Guffa:这并不让我感到惊讶。我曾经看到过C++应用程序中的发布错误,这是由于编译器将所有中间结果保留在80位FPU寄存器中导致的。 - Shog9
@Guffa:我不这么认为,因为同样适用于“double”(编辑后的答案)。 - ulrichb
如果您检查最大值和阈值之间的差异,您会发现浮点和双精度操作的差别大致相同。这支持了浮点操作使用双精度完成的结论。 - Guffa

3
这里的问题在于浮点精度。 float.MaxValue 对应的是3.40282e+038f。但是float的精度要小得多,实际上只有7位有效数字。
超过该精度范围的任何值都会“填充为零”,并且将1添加到该高位数字不会改变它。

1
嗯...它不是“充满了零”,就像1.3x10³不是“充满了零”一样。只是在使用的数据类型中无法表示加法的结果。 - Shog9

3
简而言之,两者的区别在于第39位数字,而float只能存储前7位左右。这是浮点数算术的一个特征。

0
为了使一个浮点类型的临时变量实际上保存单精度值,它必须从内存中的浮点变量加载。编译器通常允许使用比所需更高的精度来表示单精度值,并且在该值在寄存器中时倾向于这样做。当它溢出回到内存时,额外的精度就会丢失。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接