Added locale-independent numtostr
This commit is contained in:
		
							parent
							
								
									bc28942101
								
							
						
					
					
						commit
						21cae35930
					
				
					 2 changed files with 286 additions and 72 deletions
				
			
		
							
								
								
									
										179
									
								
								src/json.hpp
									
										
									
									
									
								
							
							
						
						
									
										179
									
								
								src/json.hpp
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2180,14 +2180,6 @@ class basic_json
 | 
			
		|||
    string_t dump(const int indent = -1) const
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        // fix locale problems
 | 
			
		||||
        ss.imbue(std::locale::classic());
 | 
			
		||||
 | 
			
		||||
        // 6, 15 or 16 digits of precision allows round-trip IEEE 754
 | 
			
		||||
        // string->float->string, string->double->string or string->long
 | 
			
		||||
        // double->string; to be safe, we read this value from
 | 
			
		||||
        // std::numeric_limits<number_float_t>::digits10
 | 
			
		||||
        ss.precision(std::numeric_limits<double>::digits10);
 | 
			
		||||
 | 
			
		||||
        if (indent >= 0)
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -5878,10 +5870,6 @@ class basic_json
 | 
			
		|||
    `std::setw(4)` on @a o sets the indentation level to `4` and the
 | 
			
		||||
    serialization result is the same as calling `dump(4)`.
 | 
			
		||||
 | 
			
		||||
    @note During serializaion, the locale and the precision of the output
 | 
			
		||||
    stream @a o are changed. The original values are restored when the
 | 
			
		||||
    function returns.
 | 
			
		||||
 | 
			
		||||
    @param[in,out] o  stream to serialize to
 | 
			
		||||
    @param[in] j  JSON value to serialize
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5903,22 +5891,9 @@ class basic_json
 | 
			
		|||
        // reset width to 0 for subsequent calls to this stream
 | 
			
		||||
        o.width(0);
 | 
			
		||||
 | 
			
		||||
        // fix locale problems
 | 
			
		||||
        const auto old_locale = o.imbue(std::locale::classic());
 | 
			
		||||
        // set precision
 | 
			
		||||
 | 
			
		||||
        // 6, 15 or 16 digits of precision allows round-trip IEEE 754
 | 
			
		||||
        // string->float->string, string->double->string or string->long
 | 
			
		||||
        // double->string; to be safe, we read this value from
 | 
			
		||||
        // std::numeric_limits<number_float_t>::digits10
 | 
			
		||||
        const auto old_precision = o.precision(std::numeric_limits<double>::digits10);
 | 
			
		||||
 | 
			
		||||
        // do the actual serialization
 | 
			
		||||
        j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
 | 
			
		||||
 | 
			
		||||
        // reset locale and precision
 | 
			
		||||
        o.imbue(old_locale);
 | 
			
		||||
        o.precision(old_precision);
 | 
			
		||||
        return o;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6419,6 +6394,146 @@ class basic_json
 | 
			
		|||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
    @brief locale-independent serialization for built-in arithmetic types
 | 
			
		||||
    */
 | 
			
		||||
    struct numtostr
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        numtostr(T value) 
 | 
			
		||||
        {
 | 
			
		||||
            x_write(value, std::is_integral<T>());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        operator const char*() const
 | 
			
		||||
        {
 | 
			
		||||
            return m_buf.data();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const char* c_str() const
 | 
			
		||||
        {
 | 
			
		||||
            return m_buf.data();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        static constexpr size_t s_capacity = 30;
 | 
			
		||||
        std::array<char, s_capacity + 2> m_buf{};
 | 
			
		||||
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        void x_write(T x, std::true_type)
 | 
			
		||||
        {
 | 
			
		||||
            const bool is_neg = x < 0;
 | 
			
		||||
            size_t i = 0;
 | 
			
		||||
 | 
			
		||||
            while(x and i < s_capacity) 
 | 
			
		||||
            {
 | 
			
		||||
                m_buf[i++] = '0' + abs(x % 10);
 | 
			
		||||
                x /= 10;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(i == s_capacity)
 | 
			
		||||
            {
 | 
			
		||||
                std::runtime_error(
 | 
			
		||||
                        "Number is unexpectedly long: " 
 | 
			
		||||
                        + std::to_string(x));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(i == 0) 
 | 
			
		||||
            {
 | 
			
		||||
                m_buf[i++] = '0';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(is_neg) 
 | 
			
		||||
            {
 | 
			
		||||
                m_buf[i++] = '-';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::reverse(m_buf.begin(), m_buf.begin() + i);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        void x_write(T x, std::false_type)
 | 
			
		||||
        {
 | 
			
		||||
            if (x == 0)
 | 
			
		||||
            {
 | 
			
		||||
                std::strcpy(m_buf.data(), 
 | 
			
		||||
                            std::signbit(x) ? "-0.0" : "0.0");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static constexpr auto d = 
 | 
			
		||||
                    std::numeric_limits<number_float_t>::digits10+1;
 | 
			
		||||
 | 
			
		||||
            // I'm not sure why we need that +1 above, if at all,
 | 
			
		||||
            // but without it there's a unit-test that fails
 | 
			
		||||
            // that asserts precision of the output
 | 
			
		||||
 | 
			
		||||
            static_assert(d == 6 or d == 15 or d == 16 or d == 17, "");
 | 
			
		||||
            static constexpr auto fmt = d == 6  ? "%.6g"
 | 
			
		||||
                                      : d == 15 ? "%.15g"
 | 
			
		||||
                                      : d == 16 ? "%.16g"
 | 
			
		||||
                                      : d == 17 ? "%.17g"
 | 
			
		||||
                                      :           "%.18g";
 | 
			
		||||
 | 
			
		||||
            snprintf(m_buf.data(), m_buf.size(), fmt, x);
 | 
			
		||||
 | 
			
		||||
            const std::locale loc;
 | 
			
		||||
 | 
			
		||||
            // erase thousands separator
 | 
			
		||||
            {
 | 
			
		||||
                const char sep =
 | 
			
		||||
                    std::use_facet< std::numpunct<char> >(
 | 
			
		||||
                        loc).thousands_sep(); 
 | 
			
		||||
 | 
			
		||||
                auto end = std::remove(m_buf.begin(), 
 | 
			
		||||
                                       m_buf.end(), 
 | 
			
		||||
                                       sep);
 | 
			
		||||
 | 
			
		||||
                std::fill(end, m_buf.end(), '\0');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // convert decimal point to '.'
 | 
			
		||||
            {
 | 
			
		||||
                const char decimal_point =
 | 
			
		||||
                    std::use_facet< std::numpunct<char> >(
 | 
			
		||||
                        loc).decimal_point(); 
 | 
			
		||||
 | 
			
		||||
                for(auto& c : m_buf) 
 | 
			
		||||
                {
 | 
			
		||||
                    if(decimal_point == '.') {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if(c == decimal_point) 
 | 
			
		||||
                    {
 | 
			
		||||
                        c = '.';
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // determine if need to apperd ".0"
 | 
			
		||||
            auto data_end = m_buf.begin() + strlen(m_buf.data());
 | 
			
		||||
 | 
			
		||||
            const bool value_is_int_like =
 | 
			
		||||
                std::find_if(m_buf.begin(), data_end,
 | 
			
		||||
                             [](const char c) 
 | 
			
		||||
                             {   return (c >= '0' and c <= '9') 
 | 
			
		||||
                                     or c == '-'; })
 | 
			
		||||
                == data_end;
 | 
			
		||||
            
 | 
			
		||||
            assert(data_end + 2 < m_buf.end());
 | 
			
		||||
            if(value_is_int_like) 
 | 
			
		||||
            {
 | 
			
		||||
                strcat(m_buf.data(), ".0");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
    @brief internal implementation of the serialization function
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6538,27 +6653,19 @@ class basic_json
 | 
			
		|||
 | 
			
		||||
            case value_t::number_integer:
 | 
			
		||||
            {
 | 
			
		||||
                o << m_value.number_integer;
 | 
			
		||||
                o << numtostr(m_value.number_integer).c_str();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case value_t::number_unsigned:
 | 
			
		||||
            {
 | 
			
		||||
                o << m_value.number_unsigned;
 | 
			
		||||
                o << numtostr(m_value.number_unsigned).c_str();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case value_t::number_float:
 | 
			
		||||
            {
 | 
			
		||||
                if (m_value.number_float == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    // special case for zero to get "0.0"/"-0.0"
 | 
			
		||||
                    o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    o << m_value.number_float;
 | 
			
		||||
                }
 | 
			
		||||
                o << numtostr(m_value.number_float).c_str();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2180,14 +2180,6 @@ class basic_json
 | 
			
		|||
    string_t dump(const int indent = -1) const
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        // fix locale problems
 | 
			
		||||
        ss.imbue(std::locale::classic());
 | 
			
		||||
 | 
			
		||||
        // 6, 15 or 16 digits of precision allows round-trip IEEE 754
 | 
			
		||||
        // string->float->string, string->double->string or string->long
 | 
			
		||||
        // double->string; to be safe, we read this value from
 | 
			
		||||
        // std::numeric_limits<number_float_t>::digits10
 | 
			
		||||
        ss.precision(std::numeric_limits<double>::digits10);
 | 
			
		||||
 | 
			
		||||
        if (indent >= 0)
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -5878,10 +5870,6 @@ class basic_json
 | 
			
		|||
    `std::setw(4)` on @a o sets the indentation level to `4` and the
 | 
			
		||||
    serialization result is the same as calling `dump(4)`.
 | 
			
		||||
 | 
			
		||||
    @note During serializaion, the locale and the precision of the output
 | 
			
		||||
    stream @a o are changed. The original values are restored when the
 | 
			
		||||
    function returns.
 | 
			
		||||
 | 
			
		||||
    @param[in,out] o  stream to serialize to
 | 
			
		||||
    @param[in] j  JSON value to serialize
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5903,22 +5891,9 @@ class basic_json
 | 
			
		|||
        // reset width to 0 for subsequent calls to this stream
 | 
			
		||||
        o.width(0);
 | 
			
		||||
 | 
			
		||||
        // fix locale problems
 | 
			
		||||
        const auto old_locale = o.imbue(std::locale::classic());
 | 
			
		||||
        // set precision
 | 
			
		||||
 | 
			
		||||
        // 6, 15 or 16 digits of precision allows round-trip IEEE 754
 | 
			
		||||
        // string->float->string, string->double->string or string->long
 | 
			
		||||
        // double->string; to be safe, we read this value from
 | 
			
		||||
        // std::numeric_limits<number_float_t>::digits10
 | 
			
		||||
        const auto old_precision = o.precision(std::numeric_limits<double>::digits10);
 | 
			
		||||
 | 
			
		||||
        // do the actual serialization
 | 
			
		||||
        j.dump(o, pretty_print, static_cast<unsigned int>(indentation));
 | 
			
		||||
 | 
			
		||||
        // reset locale and precision
 | 
			
		||||
        o.imbue(old_locale);
 | 
			
		||||
        o.precision(old_precision);
 | 
			
		||||
        return o;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6419,6 +6394,146 @@ class basic_json
 | 
			
		|||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
    @brief locale-independent serialization for built-in arithmetic types
 | 
			
		||||
    */
 | 
			
		||||
    struct numtostr
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        numtostr(T value) 
 | 
			
		||||
        {
 | 
			
		||||
            x_write(value, std::is_integral<T>());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        operator const char*() const
 | 
			
		||||
        {
 | 
			
		||||
            return m_buf.data();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const char* c_str() const
 | 
			
		||||
        {
 | 
			
		||||
            return m_buf.data();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        static constexpr size_t s_capacity = 30;
 | 
			
		||||
        std::array<char, s_capacity + 2> m_buf{};
 | 
			
		||||
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        void x_write(T x, std::true_type)
 | 
			
		||||
        {
 | 
			
		||||
            const bool is_neg = x < 0;
 | 
			
		||||
            size_t i = 0;
 | 
			
		||||
 | 
			
		||||
            while(x and i < s_capacity) 
 | 
			
		||||
            {
 | 
			
		||||
                m_buf[i++] = '0' + abs(x % 10);
 | 
			
		||||
                x /= 10;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(i == s_capacity)
 | 
			
		||||
            {
 | 
			
		||||
                std::runtime_error(
 | 
			
		||||
                        "Number is unexpectedly long: " 
 | 
			
		||||
                        + std::to_string(x));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(i == 0) 
 | 
			
		||||
            {
 | 
			
		||||
                m_buf[i++] = '0';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(is_neg) 
 | 
			
		||||
            {
 | 
			
		||||
                m_buf[i++] = '-';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::reverse(m_buf.begin(), m_buf.begin() + i);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        template<typename T>
 | 
			
		||||
        void x_write(T x, std::false_type)
 | 
			
		||||
        {
 | 
			
		||||
            if (x == 0)
 | 
			
		||||
            {
 | 
			
		||||
                std::strcpy(m_buf.data(), 
 | 
			
		||||
                            std::signbit(x) ? "-0.0" : "0.0");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static constexpr auto d = 
 | 
			
		||||
                    std::numeric_limits<number_float_t>::digits10+1;
 | 
			
		||||
 | 
			
		||||
            // I'm not sure why we need that +1 above, if at all,
 | 
			
		||||
            // but without it there's a unit-test that fails
 | 
			
		||||
            // that asserts precision of the output
 | 
			
		||||
 | 
			
		||||
            static_assert(d == 6 or d == 15 or d == 16 or d == 17, "");
 | 
			
		||||
            static constexpr auto fmt = d == 6  ? "%.6g"
 | 
			
		||||
                                      : d == 15 ? "%.15g"
 | 
			
		||||
                                      : d == 16 ? "%.16g"
 | 
			
		||||
                                      : d == 17 ? "%.17g"
 | 
			
		||||
                                      :           "%.18g";
 | 
			
		||||
 | 
			
		||||
            snprintf(m_buf.data(), m_buf.size(), fmt, x);
 | 
			
		||||
 | 
			
		||||
            const std::locale loc;
 | 
			
		||||
 | 
			
		||||
            // erase thousands separator
 | 
			
		||||
            {
 | 
			
		||||
                const char sep =
 | 
			
		||||
                    std::use_facet< std::numpunct<char> >(
 | 
			
		||||
                        loc).thousands_sep(); 
 | 
			
		||||
 | 
			
		||||
                auto end = std::remove(m_buf.begin(), 
 | 
			
		||||
                                       m_buf.end(), 
 | 
			
		||||
                                       sep);
 | 
			
		||||
 | 
			
		||||
                std::fill(end, m_buf.end(), '\0');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // convert decimal point to '.'
 | 
			
		||||
            {
 | 
			
		||||
                const char decimal_point =
 | 
			
		||||
                    std::use_facet< std::numpunct<char> >(
 | 
			
		||||
                        loc).decimal_point(); 
 | 
			
		||||
 | 
			
		||||
                for(auto& c : m_buf) 
 | 
			
		||||
                {
 | 
			
		||||
                    if(decimal_point == '.') {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if(c == decimal_point) 
 | 
			
		||||
                    {
 | 
			
		||||
                        c = '.';
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // determine if need to apperd ".0"
 | 
			
		||||
            auto data_end = m_buf.begin() + strlen(m_buf.data());
 | 
			
		||||
 | 
			
		||||
            const bool value_is_int_like =
 | 
			
		||||
                std::find_if(m_buf.begin(), data_end,
 | 
			
		||||
                             [](const char c) 
 | 
			
		||||
                             {   return (c >= '0' and c <= '9') 
 | 
			
		||||
                                     or c == '-'; })
 | 
			
		||||
                == data_end;
 | 
			
		||||
            
 | 
			
		||||
            assert(data_end + 2 < m_buf.end());
 | 
			
		||||
            if(value_is_int_like) 
 | 
			
		||||
            {
 | 
			
		||||
                strcat(m_buf.data(), ".0");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
    @brief internal implementation of the serialization function
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6538,27 +6653,19 @@ class basic_json
 | 
			
		|||
 | 
			
		||||
            case value_t::number_integer:
 | 
			
		||||
            {
 | 
			
		||||
                o << m_value.number_integer;
 | 
			
		||||
                o << numtostr(m_value.number_integer).c_str();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case value_t::number_unsigned:
 | 
			
		||||
            {
 | 
			
		||||
                o << m_value.number_unsigned;
 | 
			
		||||
                o << numtostr(m_value.number_unsigned).c_str();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case value_t::number_float:
 | 
			
		||||
            {
 | 
			
		||||
                if (m_value.number_float == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    // special case for zero to get "0.0"/"-0.0"
 | 
			
		||||
                    o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    o << m_value.number_float;
 | 
			
		||||
                }
 | 
			
		||||
                o << numtostr(m_value.number_float).c_str();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue