Search code examples
c++floating-pointdoubleliterals

Are there any real life situations where declaring all floating point literals as double causes real problems in C++?


Meaning, in a "normal" situation where the performance loss of type promotion is irrelevant, where nobody checks for equality on a float and where nobody declares anything funny like

void f(float x) {/*do something*/};
void f(double x) {/*do something completely different*/};

and so on.

I am aware that the general question of "C++ f suffix why?" has been asked a thousand times, but all the answers always are about technical details and contrived examples. I haven't found any satisfactory answer that shows an actually relevant situation, where the distinction is important.


Solution

  • I don't have a good "real-life" example at hand. Instead I want to try to convince you with a hopefully simple example.

    Lets assume all function with overloads for float and double do "the right thing":

    void f(float x) {/*do something*/};
    void f(double x) {/*do the same thing*/};
    

    Moreover, when only one of the overloads is available, we can call it with a literal of either type:

    void f_float(float x) {/*do something*/};
    void f_double(double x) {/*do the same thing*/};
    f_float(3.0);    // ok
    f_float(3.0f);   // ok
    f_double(3.0);   // ok
    f_double(3.0f);  // ok
    

    floats convert to doubles and vice versa.


    If all code is fine with either float or double, this is end of story. However, you don't need to do something "funny" to get the code to break.


    I haven't found any satisfactory answer that shows an actually relevant situation, where the distinction is important.

    The distinction is important when the type of the literal is used to infer the type of something else. Being able to convert between double and float is not all you need to be able to replace any float literal with a double literal without breaking code.

    In the above example f_float can be called with either 3.0 or 3.0f. Now suppose you have a function that only accepts a std::vector<float>:

    void bar(const std::vector<float>& x){}
    

    and something that given a single value, constructs a vector:

    template <typename T>    
    std::vector<T> make_vect(const T& t){ return {T{}}; }
    

    Then the type of the literal makes the code either fail or pass:

    bar(make_vect(0.3f));
    //bar(x.make_vect(0.3)); // ERROR
    

    Complete Example.

    TL;DR The whole point is just that you can convert between float and double, but in general there is no conversion available between some_template<float> and some_template<double>. The type of a literal can be relevant, even though you don't mind conversions between float and double. Changing a 3.0f to a 3.0 can make code break, that was fine otherwise