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

时间:2014-04-01 16:36:00

标签: 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";
}

结果:

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

3 个答案:

答案 0 :(得分:7)

正如评论中正确指出的那样,t的值实际上与您在下面的行中定义的1.00000不同。

使用std::setprecision(20)以更高的精度打印t将显示其实际值:1.0000001192092895508

避免这类问题的常见方法是不与1进行比较,而是与1 + epsilon进行比较,其中epsilon是一个非常小的数字,可能比你的漂浮大一或两个数量级点精度。

所以你会把你的for循环条件写成

for(t = 0; t <= 1.000001f; t += step)

请注意,在您的情况下,epsilon应至少比最大浮点错误大十倍,因为浮点数会增加十倍。

正如Muepe和Alain所指出的,t != 1.0f的原因是1/10无法用二进制浮点数精确表示。

答案 1 :(得分:5)

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

  1. 登录
  2. 指数
  3. 尾数
  4. 让我们看看它是否为32位(4字节)类型,这通常是你在C ++中为float所拥有的。

    符号只是一个简单的位置1或0,其中0表示正数,1表示其负数。如果你离开现有的每一个标准化,你也可以说0 - &gt;否定的,1 - &gt;正。

    指数可以使用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 - &gt; 1.100101至base 2 - &gt; 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. 签名 - &gt; 0 (因为其非负面)
    2. 指数 - &gt;小于0.1的第一个值是2 ^ -4。所以指数应为-4 + 127 = 123
    3. 尾数 - &gt;为此,我们检查指数为0.1的次数,然后尝试将分数转换为尾数。 0.1 /(2 ^ -4)= 0.1 / 0.0625 = 1.6。考虑尾数给出1.m,我们的尾数应为0.6。所以我们将其转换为二进制文件:
    4. 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...
      

      因此,二进制浮点环境中的0.1与基本10系统中的1/3相同。它是一个周期性的无限数。由于浮点数的空间是有限的,所以它只需要切割的第23位,因此0.1是一个小于0.1的微小位,因为浮点数中不存在数字的所有无限部分,而在23位之后是一个0,但它会四舍五入为1.

答案 2 :(得分:2)

原因是1.0 / 10.0 = 0.1不能完全用二进制表示,正如1.0 / 3.0 = 0.333 ..不能用十进制精确表示。 如果我们使用

float step = 1.0f / 8;
例如,

,结果与预期一致。

要避免此类问题,请使用mic_e的答案中所示的小偏移量。