Search code examples
cfloating-pointprecision

If float has 6 digits of precision, why can we display more than 6 digits of floats with printf?


Let us consider the following piece of code:

#include <stdio.h>
int main()
{
    float x = 0.33;
    printf("%.100f",x);

    return 0;
}

If float has 6 digits of precision, how is it possible to display more than 6 digits with printf?


Solution

  • You tried to convert the decimal fraction 0.33 to a float. But, like most decimal fractions, the number 0.33 cannot be represented exactly in the binary representation used internally by type float. The closest you can get is the binary fraction 0.0101010001111010111000011. That fraction, if we convert it back to decimal, is exactly 0.3300000131130218505859375.

    In decimal, if I tell you that you have 7 digits worth of significance, and you try to represent the number 1/3 = 0.333…, you expect to get 0.333333300000. That is, you expect to get some number of significant digits matching your original number, followed by 0's where there wasn't enough significance. And binary fractions work the same way: for type float, the binary fraction always has exactly 24 bits of significance, followed (if you like) by any number of binary 0's.

    When we convert that binary number back to decimal, we get approximately 7 digits matching the decimal number we thought we had, followed not by zeroes, but rather, by what look like random digits. For example, 1/3 as a binary float is 0.0101010101010101010101011000000000 (note 24 significant bits), which when converted to decimal is 0.333333343267440795898437500000 (note 7 accurate digits).

    When you hear that type float has approximately 7 digits of significance, that does not mean you'll get 7 digits of your original number, followed by 0's. What it means is that you'll get approximately 7 digits of your original number (but maybe 6, or maybe 8 or 9 or more), followed by some digits which probably don't match your original number but which aren't all 0, either. But that's not actually a problem, especially if (as is recommended and proper) you print this number back out rounded to a useful number of digits. When it can be a problem (though this comes up a lot) is when you print the number back out with a non-useful number of digits, with a format like %.100f, and you see some strange-looking digits which aren't all 0, and this perplexes you, as it did here.

    The fact that types float and double use a binary representation internally leads to endless surprises like this. It's not surprising that the representation is binary (we all know computers do everything in binary), but the inability of binary fractions to accurately represent the decimal fractions we're used to, now that's really surprising. See the canonical SO question Is floating point math broken? for more on this.