6

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
  • 12
    Because the for loop variable isn't really 1.0, it's 0.99999999 or something like that. Standard floating point problem. – Mark Ransom Apr 01 '14 at 16:39
  • 2
    Because you've fallen to one of the very common pitfalls associated with floating-point arithmetic. – Daniel Kamil Kozar Apr 01 '14 at 16:39
  • 2
    Because of mystical `float` arithmetic. Read young padawan: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – Cory Kramer Apr 01 '14 at 16:41
  • Your question is invalid. As your program corretly outputs, `t <= 1.0f` is false in the for loop, and false outside the for loop. The true statement is `1.00000 <= 1.0f`, which is wholly different. Thus, the following simpler code demonstrates your actual issue: http://pastebin.com/Xtphek2Q – mic_e Apr 01 '14 at 16:42
  • yes, I made another example without for loop, but just increasing the value and got the same result. –  Apr 01 '14 at 16:47
  • You should consider replacing your current question with your updated code, to make it more easily readable and thus more useful for future visitors. – mic_e Apr 01 '14 at 17:00

3 Answers3

6

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.

mic_e
  • 5,594
  • 4
  • 34
  • 49
4

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.

Muepe
  • 671
  • 3
  • 14
  • `0.1f` is actually a tiny bit greater than `0.1`, since the last digit is rounded up, not down. That _might_ depend on your FPU, though. – mic_e Apr 01 '14 at 17:18
  • you are of course right, i've "swallowed" a digit in my calculator :) – Muepe Apr 01 '14 at 17:20
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.

alain
  • 11,939
  • 2
  • 31
  • 51
  • 1
    I think the approach of adding a slight offset for the comparision is superior to rounding, not only for performance reasons, but also because rounding does not work for very large numbers, or for comparisions with non-integers. Nice addition to my answer, though. – mic_e Apr 01 '14 at 16:59
  • @mic_e I think it depends.. Yes, large numbers can't be rounded, but if equality should be tested, rounding is useful. With epsilon, equality must be tested with (x >= y - e && x <= y + e). – alain Apr 01 '14 at 17:03
  • Well, when rounding you need to consider whether you want `round()`, `ceil()` or `floor()`, and in Alexandres situation none of those actually does the job. You would need `floor(t + epsilon)`. – mic_e Apr 01 '14 at 17:05
  • @mic_e Yes, i think you're right. I'll change my answer, thanks. – alain Apr 01 '14 at 17:14