Ale*_*nzi 5 c++ for-loop floating-accuracy
有人可以解释为什么1.000000 <= 1.0f是假的吗?
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
int main(int argc, char **argv)
{
float step = 1.0f / 10;
float t;
for(t = 0; t <= 1.0f; t += step)
{
printf("t = %f\n", t);
cout << "t = " << t << "\n";
cout << "(t <= 1.0f) = " << (t <= 1.0f) << "\n";
}
printf("t = %f\n", t );
cout << "t = " << t << "\n";
cout << "(t <= 1.0f) = " << (t <= 1.0f) << "\n";
cout << "\n(1.000000 <= 1.0f) = " << (1.000000 <= 1.0f) << "\n";
}
Run Code Online (Sandbox Code Playgroud)
结果:
t = 0.000000
t = 0
(t <= 1.0f) = 1
t = 0.100000
t = 0.1
(t <= 1.0f) = 1
t = 0.200000
t = 0.2
(t <= 1.0f) = 1
t = 0.300000
t = 0.3
(t <= 1.0f) = 1
t = 0.400000
t = 0.4
(t <= 1.0f) = 1
t = 0.500000
t = 0.5
(t <= 1.0f) = 1
t = 0.600000
t = 0.6
(t <= 1.0f) = 1
t = 0.700000
t = 0.7
(t <= 1.0f) = 1
t = 0.800000
t = 0.8
(t <= 1.0f) = 1
t = 0.900000
t = 0.9
(t <= 1.0f) = 1
t = 1.000000
t = 1
(t <= 1.0f) = 0
(1.000000 <= 1.0f) = 1
Run Code Online (Sandbox Code Playgroud)
正如在注释中正确指出的那样,值t实际上与1.00000您在下面的行中定义的值不同.
以更高的精度印刷t std::setprecision(20)将显示其实际价值:1.0000001192092895508.
避免这类问题的常见方法是不要与epsilon是一个非常小的数字进行比较1,但1 + epsilon可能比浮点精度高一到两个数量级.
所以你会把你的for循环条件写成
for(t = 0; t <= 1.000001f; t += step)
Run Code Online (Sandbox Code Playgroud)
请注意,在您的情况下,epsilon应至少比最大浮点错误大十倍,因为浮点数会增加十倍.
正如Muepe和Alain所指出的那样,原因t != 1.0f是1/10无法用二进制浮点数精确表示.
C++(以及大多数其他语言)中的浮点类型是使用一种方法实现的,该方法使用以下3个组件的可用字节(例如4或8):
让我们看一下32位(4字节)类型,它通常是你在C++中用于float的东西.
该标志只是一个简单的位beeing 1或0,其中0意味着其正1,其负.如果你离开现有的每一个标准化,你也可以说0 - >负,1 - >正.
该指数可以使用8位.与我们的日常生活相反,这个指数不是用于基数10而是基数2.这意味着1作为指数不对应于10但是对应于2,而指数2表示4(= 2 ^ 2)而不是100(= 10 ^ 2).
另一个重要的部分是,对于浮点变量,我们也可能希望得到负指数,如2 ^ -1 beeing 0.5,2 ^ -2表示0.25,依此类推.因此,我们定义了一个偏离值,该偏差值从指数中减去并产生实际值.在8比特的情况下,我们选择127表示指数0表示2 ^ -127,指数表示255表示2 ^ 128.但是,这种情况有一个例外.通常,指数的两个值用于标记NaN和无穷大.因此,实指数为0到253,给出范围从2 ^ -127到2 ^ 126.
该尾数显然现在填补了剩余的23位.如果我们将尾数看作0和1的一系列,你可以想象它的值就像1.m,其中m是那些位的系列,但不是10的幂,而是2的幂.所以1.1将是1*2 ^ 0 + 1*2 ^ -1 = 1 + 0.5 = 1.5.举个例子,我们来看看下面的尾数(非常短的):
m = 100101 - > 1.100101至base 2 - > 1*2 ^ 0 + 1*2 ^ -1 + 0*2 ^ -2 + 0*2 ^ -3 + 1*2 ^ -4 + 0*2 ^ - 5 + 1*2 ^ -6 = 1*1 + 1*0.5 + 1*1/16 + 1*1/64 = 1.578125
然后使用以下公式计算浮点数的最终结果:
e*1.m*(符号-1:1)
你的循环究竟出了什么问题:你的步骤是0.1!对于基数为2的浮点数,0.1是一个非常糟糕的数字,让我们来看看为什么:
0.6 = 1 * 2^-1 + 0.1 -> m = 1
0.1 = 0 * 2^-2 + 0.1 -> m = 10
0.1 = 0 * 2^-3 + 0.1 -> m = 100
0.1 = 1 * 2^-4 + 0.0375 -> m = 1001
0.0375 = 1 * 2^-5 + 0.00625 -> m = 10011
0.00625 = 0 * 2^-6 + 0.00625 -> m = 100110
0.00625 = 0 * 2^-7 + 0.00625 -> m = 1001100
0.00625 = 1 * 2^-8 + 0.00234375 -> m = 10011001
我们可以继续这样,直到我们有23个尾数位,但我可以告诉你,你得到:
m = 10011001100110011001...
Run Code Online (Sandbox Code Playgroud)
因此,二进制浮点环境中的0.1与基数10系统中的1/3相同.它是一个周期性的无限数.由于浮点数的空间是有限的,所以它只需要切割的第23位,因此0.1是一个小于0.1的微小位,因为浮点数中不存在数字的所有无限部分,而在23位之后是一个0,但它被四舍五入到1.