Search code examples
c++boostnumber-formattingstandard-libraryboost-lexicalcast

Boost's lexical_cast From double to string Precision


I'm working with a library that unfortunately uses boost::lexical_cast to convert from a double to a string.

I need to be able to definitively mirror that behavior on my side, but I was hopping to do so without propagating boost.

Could I be guaranteed identical behavior using to_string, sprintf, or some other function contained within the standard?


Solution

  • The boost code ends up here:

                bool shl_real_type(double val, char* begin) {
                    using namespace std;
                    finish = start +
    #if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION)
                        sprintf_s(begin, CharacterBufferSize,
    #else
                        sprintf(begin, 
    #endif
                        "%.*g", static_cast<int>(boost::detail::lcast_get_precision<double>()), val);
                    return finish > start;
                }
    

    You're in luck since the precision is USUALLY compile-time constant (unless boost configures BOOST_LCAST_NO_COMPILE_TIME_PRECISION).

    Simplifying a bit and allowing for conforming, modern standard libraries:

    Mimicking Boost Lexicalcast

    #include <cstdio>
    #include <limits>
    #include <string>
    
    namespace {
        template <class T> struct lcast_precision {
            typedef std::numeric_limits<T> limits;
    
            static constexpr bool use_default_precision  = !limits::is_specialized || limits::is_exact;
            static constexpr bool is_specialized_bin     = !use_default_precision && limits::radix == 2 && limits::digits > 0;
    
            static constexpr bool is_specialized_dec     = !use_default_precision && limits::radix == 10 && limits::digits10 > 0;
            static constexpr unsigned int precision_dec  = limits::digits10 + 1U;
            static constexpr unsigned long precision_bin = 2UL + limits::digits * 30103UL / 100000UL;
    
            static constexpr unsigned value = is_specialized_bin 
                ? precision_bin 
                : is_specialized_dec? precision_dec : 6;
        };
    
        std::string mimicked(double v) {
            constexpr int prec = static_cast<int>(lcast_precision<double>::value);
    
            std::string buf(prec+10, ' ');
            buf.resize(sprintf(&buf[0], "%.*g", prec, v));
            return buf;
        }
    }
    

    Regression Tests

    To compare the results and check the assumptions:

    Live On Coliru

    #include <cstdio>
    #include <limits>
    #include <string>
    
    namespace {
        template <class T> struct lcast_precision {
            typedef std::numeric_limits<T> limits;
    
            static constexpr bool use_default_precision  = !limits::is_specialized || limits::is_exact;
            static constexpr bool is_specialized_bin     = !use_default_precision && limits::radix == 2 && limits::digits > 0;
    
            static constexpr bool is_specialized_dec     = !use_default_precision && limits::radix == 10 && limits::digits10 > 0;
            static constexpr unsigned int precision_dec  = limits::digits10 + 1U;
            static constexpr unsigned long precision_bin = 2UL + limits::digits * 30103UL / 100000UL;
    
            static constexpr unsigned value = is_specialized_bin 
                ? precision_bin 
                : is_specialized_dec? precision_dec : 6;
        };
    
        std::string mimicked(double v) {
            constexpr int prec = static_cast<int>(lcast_precision<double>::value);
    
            std::string buf(prec+10, ' ');
            buf.resize(sprintf(&buf[0], "%.*g", prec, v));
            return buf;
        }
    }
    
    #include <cmath>
    #include <iomanip>
    #include <iostream>
    #include <string>
    
    #include <boost/lexical_cast.hpp>
    
    #ifdef BOOST_LCAST_NO_COMPILE_TIME_PRECISION
    #error BOOM
    #endif
    
    #define TEST(x)                                                                                                        \
        do {                                                                                                               \
            std::cout << std::setw(45) << #x << ":\t" << (x) << "\n";                                                      \
        } while (0)
    
    std::string use_sprintf(double v) {
        std::string buf(32, ' ');
        buf.resize(std::sprintf(&buf[0], "%f", v));
        return buf;
    }
    
    void tests() {
        for (double v : {
                std::numeric_limits<double>::quiet_NaN(),
                std::numeric_limits<double>::infinity(),
               -std::numeric_limits<double>::infinity(),
                0.0,
               -0.0,
                std::numeric_limits<double>::epsilon(),
                M_PI })
        {
            TEST(v);
            TEST(std::to_string(v));
            TEST(use_sprintf(v));
            TEST(boost::lexical_cast<std::string>(v));
            TEST(mimicked(v));
    
            assert(mimicked(v) == boost::lexical_cast<std::string>(v));
        }
    }
    
    static std::locale DE("de_DE.utf8");
    
    int main() {
    
        tests();
    
        std::cout << "==== imbue std::cout\n";
        std::cout.imbue(DE);
    
        tests();
    
        std::cout << "==== override global locale\n";
        std::locale::global(DE);
    
        tests();
    }
    

    Prints

                                            v:  nan
                            std::to_string(v):  nan
                               use_sprintf(v):  nan
          boost::lexical_cast<std::string>(v):  nan
                                  mimicked(v):  nan
                                            v:  inf
                            std::to_string(v):  inf
                               use_sprintf(v):  inf
          boost::lexical_cast<std::string>(v):  inf
                                  mimicked(v):  inf
                                            v:  -inf
                            std::to_string(v):  -inf
                               use_sprintf(v):  -inf
          boost::lexical_cast<std::string>(v):  -inf
                                  mimicked(v):  -inf
                                            v:  0
                            std::to_string(v):  0.000000
                               use_sprintf(v):  0.000000
          boost::lexical_cast<std::string>(v):  0
                                  mimicked(v):  0
                                            v:  -0
                            std::to_string(v):  -0.000000
                               use_sprintf(v):  -0.000000
          boost::lexical_cast<std::string>(v):  -0
                                  mimicked(v):  -0
                                            v:  2.22045e-16
                            std::to_string(v):  0.000000
                               use_sprintf(v):  0.000000
          boost::lexical_cast<std::string>(v):  2.2204460492503131e-16
                                  mimicked(v):  2.2204460492503131e-16
                                            v:  3.14159
                            std::to_string(v):  3.141593
                               use_sprintf(v):  3.141593
          boost::lexical_cast<std::string>(v):  3.1415926535897931
                                  mimicked(v):  3.1415926535897931
    ==== imbue std::cout
                                            v:  nan
                            std::to_string(v):  nan
                               use_sprintf(v):  nan
          boost::lexical_cast<std::string>(v):  nan
                                  mimicked(v):  nan
                                            v:  inf
                            std::to_string(v):  inf
                               use_sprintf(v):  inf
          boost::lexical_cast<std::string>(v):  inf
                                  mimicked(v):  inf
                                            v:  -inf
                            std::to_string(v):  -inf
                               use_sprintf(v):  -inf
          boost::lexical_cast<std::string>(v):  -inf
                                  mimicked(v):  -inf
                                            v:  0
                            std::to_string(v):  0.000000
                               use_sprintf(v):  0.000000
          boost::lexical_cast<std::string>(v):  0
                                  mimicked(v):  0
                                            v:  -0
                            std::to_string(v):  -0.000000
                               use_sprintf(v):  -0.000000
          boost::lexical_cast<std::string>(v):  -0
                                  mimicked(v):  -0
                                            v:  2,22045e-16
                            std::to_string(v):  0.000000
                               use_sprintf(v):  0.000000
          boost::lexical_cast<std::string>(v):  2.2204460492503131e-16
                                  mimicked(v):  2.2204460492503131e-16
                                            v:  3,14159
                            std::to_string(v):  3.141593
                               use_sprintf(v):  3.141593
          boost::lexical_cast<std::string>(v):  3.1415926535897931
                                  mimicked(v):  3.1415926535897931
    ==== override global locale
                                            v:  nan
                            std::to_string(v):  nan
                               use_sprintf(v):  nan
          boost::lexical_cast<std::string>(v):  nan
                                  mimicked(v):  nan
                                            v:  inf
                            std::to_string(v):  inf
                               use_sprintf(v):  inf
          boost::lexical_cast<std::string>(v):  inf
                                  mimicked(v):  inf
                                            v:  -inf
                            std::to_string(v):  -inf
                               use_sprintf(v):  -inf
          boost::lexical_cast<std::string>(v):  -inf
                                  mimicked(v):  -inf
                                            v:  0
                            std::to_string(v):  0,000000
                               use_sprintf(v):  0,000000
          boost::lexical_cast<std::string>(v):  0
                                  mimicked(v):  0
                                            v:  -0
                            std::to_string(v):  -0,000000
                               use_sprintf(v):  -0,000000
          boost::lexical_cast<std::string>(v):  -0
                                  mimicked(v):  -0
                                            v:  2,22045e-16
                            std::to_string(v):  0,000000
                               use_sprintf(v):  0,000000
          boost::lexical_cast<std::string>(v):  2,2204460492503131e-16
                                  mimicked(v):  2,2204460492503131e-16
                                            v:  3,14159
                            std::to_string(v):  3,141593
                               use_sprintf(v):  3,141593
          boost::lexical_cast<std::string>(v):  3,1415926535897931
                                  mimicked(v):  3,1415926535897931
    

    Note that mimicked and boost::lexical_cast<std::string>(double) result in exactly the same output each time.