问题描述
我知道我们不应该在循环中使用浮点数。但是有人可以向我解释当我们有一个循环并且我们将一个小数加到一个大数直到达到允许循环终止的某个值时会发生什么吗? 我想这可能会导致潜在的错误。但除此之外?
单精度 (float) 和双精度 (double) 浮点数会是什么样子?我想在 double 类型中会出现更多的舍入错误。有人能给我一个例子(C++中最好的),因为我不知道如何开始......
解决方法
在使用 IEEE-754 算术和 float
的“单一”(binary32)格式的 C++ 实现中,此代码打印“count = 3”:
int count = 0;
for (float f = 0; f < .3f; f += .1f)
++count;
std::cout << "count = " << count << ".\n";
但是这段代码打印出“count = 4”:
int count = 0;
for (float f = 0; f < .33f; f += .11f)
++count;
std::cout << "count = " << count << ".\n";
在第一个示例中,源文本 .1f
被转换为 0.100000001490116119384765625,这是 float
中可表示的值,接近 .1。源文本 .3f
被转换为 0.300000011920928955078125,即最接近 0.3 的 float
值。将此转换后的 .1f
值添加到 f
会产生 0.100000001490116119384765625,然后是 0.20000000298023223876953125,然后是 0.3000000195207 和 0.3000000195207 点,循环停止在 {8195209 和 {1}207}
在第二个示例中,f < .3f
转换为 0.10999999940395355224609375,而 .11f
转换为 0.3300000131130218505859375。在这种情况下,将 .33f
的转换值与 .11f
相加会产生 0.10999999940395355224609375,然后是 0.2199999988079071044921875,然后是 0.32999969896325089注意,由于四舍五入,这个f
相加3次的结果是0.329999983310699462890625,小于.11f
(0.3300000131130218505859375),所以.33f
继续为真,循环继续.
这类似于以两位十进制格式添加⅓,循环边界为三分之三(即 1)。如果我们有 f < .33f
,源文本中的 for (f = 0; f < 1; f += ⅓)
必须转换为 0.33(两位十进制)。然后 ⅓
将逐步通过 .33、.66 和 .99。循环直到达到 1.32 才会停止。二进制浮点运算中也会出现同样的舍入问题。
当循环中添加的数量相对于大数是小数时,这些舍入问题更大。首先,加法会更多,所以舍入误差会更多,并且可能会累积。其次,由于较大的数字需要较大的指数才能以浮点格式对它们进行缩放,因此与较小的数字相比,它们的绝对精度较低。这意味着相对于添加的小数,四舍五入必须更大。所以舍入误差的幅度更大。
然后,即使循环最终终止,由于累积错误,每次迭代中 f
的值可能与期望值相差甚远。如果 f
用于循环内的计算,则计算可能未使用所需的值并可能产生不正确的结果。
随着值的增加,两个浮点值之间的差异也会增加。有一点是 i+1
产生相同的值。
考虑这个代码:
#include <iostream>
int main()
{
float i = 0;
while (i != i + 1) i++;
std::cout << i << std::endl;
return 0;
}
while (i != i + 1)
应该是一个无限循环,但对于浮点变量,它不是。
上面的代码在 https://godbolt.org/z/7xf8n8
1.67772e+07
所以,for (float f = 0; f < 2e7; f++)
是一个无限循环。
你可以自己用double
试试,价值更大。