Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you use a stringstream to safely convert bidirectionally?

I have this case where a double is converted to a string using a stringstream. Now, I have to get back that value somewhere else (no, I have no access to the original double), so I have to parse the formatted string.

Ofcourse, I can read it just with a stringstream as well, but is that safe? Will it always work, for all values?

std::ostringstream doubleToString;
double myDouble = 3.14;
doubleToString << myDouble;
std::string convertedDouble = doubleToString.str();

std::istringstream stringToDouble(convertedDouble);
double newDouble;
stringToDouble >> newDouble;

In the example above, will myDouble always be equal to newDouble?

Note: marginal differences (0.00000000001) are not my concern.

like image 529
W. Goeman Avatar asked Sep 30 '22 12:09

W. Goeman


1 Answers

No, it will not always work. You can prove this to yourself using this minor modification of your code:

#include <sstream>
#include <iostream>
#include <string>
#include <cassert>

int main()
{
    std::ostringstream doubleToString;
    double zero = 0;
    double myDouble = 3.14/zero;
    doubleToString << myDouble;
    std::string convertedDouble = doubleToString.str();
    std::cout << "convertedDouble = " << convertedDouble << std::endl;

    std::istringstream stringToDouble(convertedDouble);
    double newDouble;
    stringToDouble >> newDouble;
    std::cout << "newDouble = " << newDouble << std::endl;
    assert(newDouble == myDouble);
}

When I run this code on my machine, I get this output:

convertedDouble = inf
newDouble = 0
xlat: xlat.cpp:19: int main(): Assertion `newDouble == myDouble' failed.
Aborted (core dumped)

Update:

Note that if you always start with a double in the range of representable floating point numbers on your particular machine (that is std::numeric_limits<double>::min() <= x <= std::numeric_limits<double>::max()) it still isn't guaranteed. As an example of this try the code above with double myDouble = 1/3.0;. The issue is one of precision when emitting the double to a string. We can try to address that by modifying the code to use this:

doubleToString << 
    std::setprecision(std::numeric_limits<double>::digits10) << myDouble;

This has the effect of setting the precision to as many decimal digits as are needed for an integer value that will always survive transformation into and back from a double. However, it still won't work for all double values!

double myDouble = pow(1/3.0, 300);

This value will cause the assert to trigger. The reason is that the digits10 is only for the mantissa portion of the floating point number and doesn't represent the exponent. Getting completely pedantic here, we can set the right value as:

constexpr int dblprecision = std::numeric_limits<double>::digits10 
        + ceil(log10(std::numeric_limits<double>::max_exponent10));
doubleToString << std::setprecision(dblprecision) << myDouble;

Note that this is only needed in the double-to-string conversion direction and not the other way.

So with the understanding that the input value must be in the indicated range, and provided you set the precision for the conversion to string, I think this will always result in the original double, assuming no bugs in the implementation of the compiler or library.

like image 112
Edward Avatar answered Oct 03 '22 02:10

Edward