5

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 :).

Asheh
  • 1,547
  • 16
  • 25
  • 1
    The `<< '\0'` is redundant. – Emil Laine Sep 10 '15 at 07:24
  • Depending on if you're compiling in C++11 mode or not, the stream input operator `>>` uses either `strtod` or `scanf` to parse the number. That may make some difference from using `atof`. (See e.g. [this old answer of mine for links to references](http://stackoverflow.com/a/13379073/440558)) – Some programmer dude Sep 10 '15 at 07:27
  • 1
    [Not reproducible one](http://coliru.stacked-crooked.com/a/9912ab957d793d3c) and [two](http://pastebin.com/qfnYFxE0) – n. m. could be an AI Sep 10 '15 at 07:34
  • With `GCC 5.1.1` I get the same value with both (73.3099976), using either `C++11` or `C++03`. – Galik Sep 10 '15 at 07:38
  • 2
    @n.m. Reproducable with MSV2015: `73.3100051879883`, `73.3099975585938` – Simon Kraemer Sep 10 '15 at 07:38
  • `basic_istream` has an `operator>>(float& _Val)` which at some point uses `extern _CRTIMP2_PURE float __CLRCALL_PURE_OR_CDECL _Stofx(const char *, _Out_opt_ _Deref_post_opt_valid_ char **, long, int *);. Meanwhile `atof` returns a `double` and not a float value. `atof`returns a correct value of `73.31` which is then changed to `73.3099975585938` when converting `double` to `float`. **MSVC2015** – Simon Kraemer Sep 10 '15 at 07:42
  • What platform and compiler do you use? – schorsch_76 Sep 10 '15 at 07:44
  • Sorry I should have stated that. MSVC2013 – Asheh Sep 10 '15 at 07:44
  • With MSVC2013 i get a warning: floatVal = std::atof(val); Conversion from double to float. It might be the same on your platform. I fi Change the float to a double, there is no warning and no difference. – schorsch_76 Sep 10 '15 at 07:48
  • we can safely cast to a float in this case, no? – Asheh Sep 10 '15 at 07:49
  • 2
    If you cast from double to float you loose precision. That is the point here. – schorsch_76 Sep 10 '15 at 07:51
  • This seems to be introduced in vc11. No difference in vc10. – n. m. could be an AI Sep 10 '15 at 08:13
  • floating point arithmetics, they are not so accurate as you may think – David Haim Sep 10 '15 at 11:24
  • 73.31 converts to 73.30999755859375 as a float (http://www.exploringbinary.com/floating-point-converter/ ), and rounded to 9 digits that's 73.3099976. So what you get from atof() is correct, despite double rounding (from string to double and then double to float). – Rick Regan Sep 10 '15 at 13:54
  • And conversion through stringstream has issues; see http://www.exploringbinary.com/incorrect-round-trip-conversions-in-visual-c-plus-plus/ and https://connect.microsoft.com/VisualStudio/feedback/details/1540816 – Rick Regan Sep 10 '15 at 14:00

1 Answers1

2

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

Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
  • i tested this code on gcc, clang and vc. And the only explanation that i see is the implementation of istream>>float. Looks like VC does it right (using float) while all the others use double and then convert result to float. – Alexander Sep 10 '15 at 08:09
  • @Alexander It's not clear why you deem the VC implementation "right". It does not convert the decimal number to its closest binary representation, while all others do. – n. m. could be an AI Sep 10 '15 at 08:26
  • Why would you say that "the real culprit is the cast from double to float"? The bad case is the istream `>>` extraction. `atof` followed by cast delivers correct results, `>>` does not. – n. m. could be an AI Sep 10 '15 at 08:28
  • so what do u expect from stream>>float? we have several options: 1. read into double then convert to float. 2. read into long double and convert to float 3. read into float directly. What is the right way? – Alexander Sep 10 '15 at 08:33
  • @n.m. "It does not convert the decimal number to its closest binary representation, while all others do" Is that so? When you look at your tests you will see that gcc evaluates to `73.3099975585938`. So the cast after `atof` is correct. – Simon Kraemer Sep 10 '15 at 08:41
  • Yes, the cast after atof is correct, but you sre calling it "the culprit" as if it's guilty of something. – n. m. could be an AI Sep 10 '15 at 08:43
  • BTW float f = 73.3f; f += 0.01f; // gives 73.3100051879883. – Alexander Sep 10 '15 at 08:43
  • Yeah, my wording isn't the best here. I'm already thinking about how to rephrase it. – Simon Kraemer Sep 10 '15 at 08:44