C/C++:1.00000 <= 1.0f = False

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)

mic*_*c_e 7

正如在注释中正确指出的那样,值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.0f1/10无法用二进制浮点数精确表示.


Mue*_*epe 5

C++(以及大多数其他语言)中的浮点类型是使用一种方法实现的,该方法使用以下3个组件的可用字节(例如4或8):

  1. 标志
  2. 指数
  3. 尾数

让我们看一下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是一个非常糟糕的数字,让我们来看看为什么:

  1. sign - > 0(因为它是非负的)
  2. 指数 - >小于0.1的第一个值是2 ^ -4.所以指数应该是-4 + 127 = 123
  3. 尾数 - >为此,我们检查指数为0.1的次数,然后尝试将分数转换为尾数.0.1 /(2 ^ -4)= 0.1/0.0625 = 1.6.考虑尾数给出1.m,我们的尾数应为0.6.所以我们将其转换为二进制:

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.