Search code examples
c++ieee-754

Losing precision when casting float to double, even for values that have precise binary representations


The canonical example used for explaining the binary vs decimal representation of floating points is the value 0.3:

float asFloat = 0.3;    // <-- 0.300000012
double asDouble = 0.3;  // <-- 0.29999999999999999
double asCastedDouble = static_cast<double>( asFloat );  // <-- 0.30000001192092896

asFloat and asDouble are the closest possible binary representation possible of the literal 0.3. A naïve look might be surprised at the value of asCastedDouble, but then we remember that it’s the closest representation of the float value, not of the literal, so that makes sense. I do wonder, however, why the casted double representation is not identical to the float representation (which is already in binary) with added zero bits in the mantissa, which in decimal would translate to 0.300000011200000000, but that is a separate question.

Consider now a second example for a different literal:

float asFloat2 = -0.488730401;    // <-- -0.488730401 
double asDouble2 = -0.488730401;  // <-- -0.48873040099999998
double asCastedDouble2 = static_cast<double>( asFloat2 );   // <-- -0.48873040080070496

This literal happens to be accurately representable in binary, hence asFloat2 is the same as our literal value. Just like in the last example, I wonder why asDouble2 is “less” accurate than asFloat2 instead of being identical with extra zero bits in the mantissa, but I’ll let it slide. The question is now: why is asCastedDouble2 the value that it is? It is not the same setup as the previous example, as in there we had a precision error introduced in the float, which was then carried into the double. In this case, however, the float value is identical to the literal, so where does the value of as asCastedDouble2 come from? It is clearly not the closest binary representation possible, since asDouble2 shows us a closer representation?

So I guess my question is twofold:

  1. why can certain numbers representible as a float not be representible as a double?
  2. probably related, why does it appear like we lose precision when converting from float to double?

Solution

  • I do wonder, however, why the casted double representation is not identical to the float representation

    They are identical, you just don't print enough digits.

    #include <iomanip>
    #include <iostream>
    
    int main()
    {
        float asFloat = 0.3; 
        double asDouble = 0.3;
        double asCastedDouble = static_cast<double>( asFloat );
        std::cout << std::setprecision(1000);
        std::cout << asFloat << '\n';        // 0.300000011920928955078125
        std::cout << asDouble << '\n';       // 0.299999999999999988897769753748434595763683319091796875
        std::cout << asCastedDouble << '\n'; // 0.300000011920928955078125
    }
    

    Consider now a second example ... This literal happens to be accurately representable in binary

    No it isn't. You're not printing enough digits.

    #include <iomanip>
    #include <iostream>
    
    int main()
    {
        float asFloat = -0.488730401; 
        double asDouble = -0.488730401;
        double asCastedDouble = static_cast<double>( asFloat );
        std::cout << std::setprecision(1000);
        std::cout << asFloat << '\n';        // -0.4887304008007049560546875
        std::cout << asDouble << '\n';       // -0.4887304009999999809821247254149056971073150634765625
        std::cout << asCastedDouble << '\n'; // -0.4887304008007049560546875
    }