Search code examples
c++floating-pointprecisionstringstreamatof

atof and stringstream produce different results


I have been looking into a problem whereby I am converting a float to a human readable format, and back. Namely a string. I have ran into issues using stringstream and found that atof produces "better" results.

Notice, I do not print out the data in this case, I used the debugger to retrieve the values:

    const char *val = "73.31";
    std::stringstream ss;
    ss << val << '\0';
    float floatVal = 0.0f;
    ss >> floatVal; //VALUE IS 73.3100052

    floatVal = atof(val); //VALUE IS 73.3099976

There is probably a reasonable explanation to this. If anybody can enlighten me I'd be greatful :).


Solution

  • Answer is based on the assumption that OP uses MSVC

    atof is indeed better in reading floating point values than istream.

    See this example:

    #include <iostream>
    #include <sstream>
    #include <iomanip>
    #include <cstdlib>
    
    int main()
    {
        const char *val = "73.31";
        std::stringstream ss;
        ss << val;
        float floatVal = 0.0f;
        ss >> floatVal;
        std::cout << "istream>>(float&)                       :" << std::setw(18) << std::setprecision(15) << floatVal << std::endl;
    
        double doubleVal = atof(val);
        std::cout << "double atof(const char*)                :" << std::setw(18) << std::setprecision(15) << doubleVal << std::endl;
    
        floatVal = doubleVal;
        std::cout << "(float)double atof(const char*)         :" << std::setw(18) << std::setprecision(15) << floatVal << std::endl;
    
        doubleVal = floatVal;
        std::cout << "(double)(float)double atof(const char*) :" << std::setw(18) << std::setprecision(15) << floatVal << std::endl;
    }
    

    Output:

    istream>>(float&)                       :  73.3100051879883
    double atof(const char*)                :             73.31
    (float)double atof(const char*)         :  73.3099975585938
    (double)(float)double atof(const char*) :  73.3099975585938
    

    The compiler even warns about the conversion from doubleto float this:

    warning C4244: '=': conversion from 'double' to 'float', possible loss of data
    

    I also found this page: Conversions from Floating-Point Types


    Update:

    The value 73.3099975585938 seems to be the correct float interpretation of the double value 73.31.


    Update: istream>>(double&) works correctly as well:

    #include <iostream>
    #include <sstream>
    #include <iomanip>
    #include <cstdlib>
    
    int main()
    {
        const char *val = "73.31";
        std::stringstream ss;
        ss << val;
        double doubleVal = 0.0f;
        ss >> doubleVal;
        std::cout << "istream>>(double&) :" << std::setw(18) << std::setprecision(15) << doubleVal << std::endl;
    }
    

    Output:

    istream>>(double&) :             73.31
    

    For arithmetic types istream::operator>> uses num_get::get. num_get::get should be using something like scanf("%g") for float source

    BUT:

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <string>
    #include <iomanip>
    #include <cstdlib>
    
    
    int main()
    {
        std::string s = "73.31";
        float f = 0.f;
        sscanf(s.c_str(), "%g", &f);
        std::cout << std::setw(18) << std::setprecision(15) << f << std::endl;
    }
    

    Output:

    73.3099975585938
    

    For me this looks like there might be a bug in Microsoft num_get