boost lexical cast double to string giving invalid results

855 Views Asked by At

I am trying this:

std::cout << boost::lexical_cast<std::string>(0.0009) << std::endl;

and expecting the output to be:

0.0009

But the output is:

0.00089999999999999998

g++ version: 5.4.0, Boost version: 1.66

What can I do to make it print what it's been given.

3

There are 3 best solutions below

0
On BEST ANSWER

You can in fact override the default precision:

Live On Coliru

#include <boost/lexical_cast.hpp>

#ifdef BOOST_LCAST_NO_COMPILE_TIME_PRECISION
#    error unsupported
#endif

template <> struct boost::detail::lcast_precision<double> : std::integral_constant<unsigned, 5> { };

#include <string>
#include <iostream>

int main() {
    std::cout << boost::lexical_cast<std::string>(0.0009) << std::endl;
}

Prints

0.0009

However, this is both not supported (detail::) and not flexible (all doubles will come out this way now).

The Real Problem

The problem is loss of accuracy converting from the decimal representation to the binary representation. Instead, use a decimal float representation:

Live On Coliru

#include <boost/lexical_cast.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <string>
#include <iostream>

using Double = boost::multiprecision::cpp_dec_float_50;

int main() {
    Double x("0.009"),
           y = x*2,
           z = x/77;

    for (Double v : { x, y, z }) {
        std::cout << boost::lexical_cast<std::string>(v) << "\n";
        std::cout << v << "\n";
    }
}

Prints

0.009
0.009
0.018
0.018
0.000116883
0.000116883
2
On

boost::lexical_cast doesn't allow you to specify the precision when converting a floating point number into its string representation. From the documentation

For more involved conversions, such as where precision or formatting need tighter control than is offered by the default behavior of lexical_cast, the conventional std::stringstream approach is recommended.

So you could use stringstream

double d = 0.0009;
std::ostringstream ss;
ss << std::setprecision(4) << d;
std::cout << ss.str() << '\n';

Or another option is to use the boost::format library.

std::string s = (boost::format("%1$.4f") % d).str();
std::cout << s << '\n';

Both will print 0.0009.

1
On

0.0009 is a double precision floating literal with, assuming IEEE754, the value

0.00089999999999999997536692664112933925935067236423492431640625

That's what boost::lexical_cast<std::string> sees as the function parameter. And the default precision setting in the cout formatter is rounding to the 17th significant figure:

0.00089999999999999998

Really, if you want exact decimal precision, then use a decimal type (Boost has one), or work in integers and splice in the decimal separator yourself. But in your case, given that you're simply outputting the number with no complex calculations, rounding to the 15th significant figure will have the desired effect: inject

std::setprecision(15)

into the output stream.