Boost::Lexical_cast conversion to float changes da

2019-08-12 02:23发布

问题:

I am receiving data from MySQL and try to play with it. The data received is in m_caracs and then I try to cut every sub-parts of this stream in other float.

Let's see the code :

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include <string>

std::string m_sten;
std::string m_feal;
std::string m_felt;
std::string m_inte;
std::string m_sag;
std::string m_ende;
std::string m_asko;
std::string m_vit;

void test(bool mon)
{
    std::string m_caracs = "f21.0i51.24v58.65c47.3s5.54d57.68e54.23h24.42";
    if (mon == 0)
    {
        std::vector<std::string> charmps;
        boost::split(charmps, m_caracs, boost::is_any_of("fivcsdeh"));
        m_sten = boost::lexical_cast<float>(charmps[1]);
        m_feal = boost::lexical_cast<float>(charmps[2]);
        m_felt = boost::lexical_cast<float>(charmps[3]);
        m_inte = boost::lexical_cast<float>(charmps[4]);
        m_sag = boost::lexical_cast<float>(charmps[5]);
        m_ende = boost::lexical_cast<float>(charmps[6]);
        m_asko = boost::lexical_cast<float>(charmps[7]);
        m_vit = boost::lexical_cast<float>(charmps[8]);
        std::cout << m_caracs << std::endl;
    }
    else
    {
        std::cout << m_caracs << std::endl;
        m_caracs = "f" + boost::lexical_cast<std::string>(m_sten) +
                   "i" + boost::lexical_cast<std::string>(m_feal) +
                   "v" + boost::lexical_cast<std::string>(m_felt) +
                   "c" + boost::lexical_cast<std::string>(m_inte) +
                   "s" + boost::lexical_cast<std::string>(m_sag) +
                   "d" + boost::lexical_cast<std::string>(m_ende) +
                   "e" + boost::lexical_cast<std::string>(m_asko) +
                   "h" + boost::lexical_cast<std::string>(m_vit);
        std::cout << m_caracs << std::endl;
    }
}

int main()
{
    test(1);
    test(0);
}

You can see that f21.0i51.24v58.65c47.3s5.54d57.68e54.23h24.42 becomes f21.0i51.24v58.65c47.3s5.54d57.68e54.23h24.42. This is exactly what I want. The problem is, I have that :

I don't know where it comes from. The only change is that m_caracs is a stream received from a database. Is that a conversion problem ?

回答1:

The problem is that one time you treat the split tokens as strings (leaving them unchanged), and sometimes you convert to float.

The conversion to float creates an inexact binary floating point representation.

To avoid it, don't use a binary floating point representation, but use a decimal representation with sufficient precision to accurately store your decimal input representation.

Use, e.g. boost::multiprecision::cpp_dec_float

Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>

#include <iostream>

namespace qi = boost::spirit::qi;
typedef boost::multiprecision::cpp_dec_float_50 Float;

int main()
{
    std::string const m_caracs("f21.0i51.24v58.65c47.3s5.54d57.68e54.23h24.42");
    std::cout << m_caracs << '\n';

    Float m_sten, m_feal, m_felt, m_inte, m_sag, m_ende, m_asko, m_vit;

    //auto num = qi::as_string[qi::raw[qi::double_]]; // this would parse exponents like 57.68e54
    auto num = boost::proto::deep_copy(qi::as_string[+qi::char_("-+0-9.")]);
    if (qi::parse(m_caracs.begin(), m_caracs.end(),
                'f' >> num >> 'i' >> num >> 'v' >> num >>
                'c' >> num >> 's' >> num >> 'd' >> num >>
                'e' >> num >> 'h' >> num,
                m_sten, m_feal, m_felt, m_inte, m_sag, m_ende, m_asko, m_vit
             ))
    {
        std::cout <<
            'f' << m_sten <<
            'i' << m_feal <<
            'v' << m_felt <<
            'c' << m_inte <<
            's' << m_sag  <<
            'd' << m_ende <<
            "e" << m_asko <<
            'h' << m_vit  << '\n';
    }
}

PS Note there is also a problem with the input format! 57.68e54 is a valid floating point number (e.g. for lexical_cast). Also, there could be issues with NaN or Inf

Note: in the example above you'd probably want to use qi::real_parser<Float, custom_real_policies<Float> > to parse directly into a cpp_dec_float, and not recognizing an exponent (like e54)