简体   繁体   中英

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

Can someone explain why 1.000000 <= 1.0f is false?

The code:

#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";
}

The result:

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

As correctly pointed out in the comments, the value of t is not actually the same 1.00000 that you are defining in the line below.

Printing t with higher precision with std::setprecision(20) will reveal its actual value: 1.0000001192092895508 .

The common way to avoid these kinds of issues is to compare not with 1 , but with 1 + epsilon , where epsilon is a very small number, that is maybe one or two magnitudes greater than your floating point precision.

So you would write your for loop condition as

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

Note that in your case, epsilon should be atleast ten times greater than the maximum possible floating point error, as the float is added ten times.

As pointed out by Muepe and Alain, the reason for t != 1.0f is that 1/10 can not be precisely represented in binary floating point numbers.

Floating point types in C++ (and most other languages) are implemented using an approach that uses the available bytes (for example 4 or 8) for the following 3 components:

  1. Sign
  2. Exponent
  3. Mantissa

Lets have a look at it for a 32 bit (4 byte) type which often is what you have in C++ for float.

The sign is just a simple bit beeing 1 or 0 where 0 could mean its positive and 1 that its negative. If you leave every standardization away that exists you could also say 0 -> negative, 1 -> positive.

The exponent could use 8 bits. Opposed to our daily life this exponent is not ment to be used to the base 10 but base 2. That means 1 as an exponent does not correspond to 10 but to 2, and the exponent 2 means 4 (=2^2) and not 100 (=10^2).

Another important part is, that for floating point variables we also might want to have negative exponents like 2^-1 beeing 0.5, 2^-2 for 0.25 and so on. Thus we define a bias value that gets subtracted from the exponent and yields the real value. In this case with 8 bits we'd choose 127 meaning that an exponent of 0 gives 2^-127 and an exponent of 255 means 2^128. But, there is an exception to this case. Usually two values of the exponent are used to mark NaN and infinity. Therefore the real exponent is from 0 to 253 giving a range from 2^-127 to 2^126.

The mantissa obviously now fills up the remaining 23 bits. If we see the mantissa as a series of 0 and 1 you can imagine its value to be like 1.m where m is the series of those bits, but not in powers of 10 but in powers of 2 . So 1.1 would be 1 * 2^0 + 1 * 2^-1 = 1 + 0.5 = 1.5. As an example lets have a look at the following mantissa (a very short one):

m = 100101 -> 1.100101 to 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

The final result of a float is then calculated using:

e * 1.m * (sign ? -1 : 1)

What exactly is going wrong in your loop: Your step is 0.1! 0.1 is a very bad number for floating point numbers to base 2, lets have a look why:

  1. sign -> 0 (as its non-negative)
  2. exponent -> The first value smaller than 0.1 is 2^-4. So the exponent should be -4 + 127 = 123
  3. mantissa -> For this we check how many times the exponent is 0.1 and then try to convert the fraction to a mantissa. 0.1 / (2^-4) = 0.1/0.0625 = 1.6. Considering the mantissa gives 1.m our mantissa should be 0.6. So lets convert that to binary:

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

We could continue like thiw until we have our 23 mantissa bits but i can tell you that you get:

m = 10011001100110011001...

Therefore 0.1 in a binary floating point environment is like 1/3 is in a base 10 system. Its a periodic infinite number. As the space in a float is limited there comes the 23rd bit where it just has to cut of, and therefore 0.1 is a tiny bit greater than 0.1 as there are not all infinite parts of the number in the float and after 23 bits there would be a 0 but it gets rounded up to a 1.

The reason is that 1.0/10.0 = 0.1 can not be represented exactly in binary, just as 1.0/3.0 = 0.333.. can not be represented exactly in decimals. If we use

float step = 1.0f / 8;

for example, the result is as expected.

To avoid such problems, use a small offset as shown in the answer of mic_e.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM