c++boostfloating-pointlexical-cast

Boost::Lexical_cast conversion to float changes data


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 :

enter image description here

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 ?


Solution

  • 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)