spirit x3 double_ パーサの精度が悪い件

boost 1.72 の spirit x3 の double_ パーサを使用していたところ、どうも値がちょっとおかしい。
円周率の小数点以下16桁までの文字列 "3.1415926535897932" で試してみました。
VC14.16.27023 (Visual Studio 2017 15.9.22) 使用。

#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <iostream>
#include <string>

double atof_x3_double_(std::string const& str)
{
    std::string::const_iterator iter = str.begin();
    double result;
    bool success = boost::spirit::x3::parse(iter, str.end(), boost::spirit::x3::double_, result);
    if (!success || iter != str.end())
        throw std::runtime_error("Parse failed.");
    return result;
}

void dump(std::ostream& os, double const& x)
{
    typedef unsigned __int64 uint64;
    BOOST_STATIC_ASSERT(sizeof(uint64) == sizeof(double));
    os << std::hex << reinterpret_cast<uint64 const&>(x);
}

int main()
{
    using namespace std;
    try {
        char const org[] = "3.1415926535897932"; // 円周率の小数点以下16桁まで
        double x = atof_x3_double_(org);
        double a = atof(org);
        
        cout << setprecision(17);
        cout << "org:    \t" << org << endl;
        cout << "double_:\t" << x << '\t';
        dump(cout, x);
        cout << endl;
        cout << "atof:   \t" << a << '\t';
        dump(cout, a);
        cout << endl;
    }
    catch (exception const& x) {
        cerr << x.what() << endl;
    }
}

spirit に合わせて全体的に const は後置になってますが、どっちでも大丈夫です。

出力

org:    	3.1415926535897932
double_:	3.1415926535897927	400921fb54442d17
atof:   	3.1415926535897931	400921fb54442d18

atof を通した場合と比べて、下位1ビットの違いだけですが精度が悪いです。

なので、double_ でパースした部分を文字列として取り出して、それを std::stof に通した結果を返すパーサ my_double を定義してみました。

namespace x3 = boost::spirit::x3;

struct my_double_class {};
typedef x3::rule<my_double_class, double> my_double_type;
my_double_type const my_double = "my_double";

static std::string::const_iterator my_double_pos;

auto const my_double_save_pos =
    [] (auto const& ctx) { my_double_pos = _where(ctx).begin(); };

auto const my_double_act =
    [] (auto& ctx) {
        _val(ctx) = std::stod(std::string(my_double_pos, _where(ctx).begin()));
    };

auto const my_double_def =
        x3::eps     [my_double_save_pos]
    >>  x3::double_ [my_double_act]
    ;

BOOST_SPIRIT_DEFINE(my_double);

これで boost::spirit::x3::double_ の代わりに my_double を使えば atof と同じ値になります。効率はともかくとして。

[2020/06/12] stof だったところを stod に書き換えました。