From 21cae35930a734fa726b1dc7cc73365b2b6c41b2 Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Sun, 4 Dec 2016 01:27:22 -0500 Subject: [PATCH 1/9] Added locale-independent numtostr --- src/json.hpp | 179 ++++++++++++++++++++++++++++++++++++---------- src/json.hpp.re2c | 179 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 286 insertions(+), 72 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 6fed0a122..a4f28e03f 100644 --- a/src/json.hpp +++ b/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::digits10 - ss.precision(std::numeric_limits::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::digits10 - const auto old_precision = o.precision(std::numeric_limits::digits10); - // do the actual serialization j.dump(o, pretty_print, static_cast(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 + numtostr(T value) + { + x_write(value, std::is_integral()); + } + + 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 m_buf{}; + + template + 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 + 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::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 >( + 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 >( + 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; } diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index c6a99b89f..b9716c9e3 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -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::digits10 - ss.precision(std::numeric_limits::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::digits10 - const auto old_precision = o.precision(std::numeric_limits::digits10); - // do the actual serialization j.dump(o, pretty_print, static_cast(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 + numtostr(T value) + { + x_write(value, std::is_integral()); + } + + 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 m_buf{}; + + template + 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 + 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::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 >( + 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 >( + 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; } From 219785639595d5f0276df1b9e21d14956233c3da Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Sun, 4 Dec 2016 01:38:23 -0500 Subject: [PATCH 2/9] Fixed suffixing .0 and modified the unit tests accordingly --- src/json.hpp | 5 +++-- src/json.hpp.re2c | 5 +++-- test/src/unit-regression.cpp | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index a4f28e03f..a886c4369 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -6520,8 +6520,9 @@ class basic_json 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 == '-'; }) + { return c == '.' + or c == 'e' + or c == 'E'; }) == data_end; assert(data_end + 2 < m_buf.end()); diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index b9716c9e3..dd7af5dde 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -6520,8 +6520,9 @@ class basic_json 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 == '-'; }) + { return c == '.' + or c == 'e' + or c == 'E'; }) == data_end; assert(data_end + 2 < m_buf.end()); diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index e04513caf..ada1e1042 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -394,9 +394,9 @@ TEST_CASE("regression tests") //issue #230 //CHECK(j2b.dump() == "23.42"); - CHECK(j3a.dump() == "10000"); - CHECK(j3b.dump() == "10000"); - CHECK(j3c.dump() == "10000"); + CHECK(j3a.dump() == "10000.0"); + CHECK(j3b.dump() == "10000.0"); + CHECK(j3c.dump() == "10000.0"); //CHECK(j3b.dump() == "1E04"); // roundtrip error //CHECK(j3c.dump() == "1e04"); // roundtrip error } From 509447b4d5ca42c03d15e4de810c528c0fb25986 Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Mon, 5 Dec 2016 19:03:39 -0500 Subject: [PATCH 3/9] Small bufix related to creation of fmt string for snprintf --- src/json.hpp | 17 ++++++++--------- src/json.hpp.re2c | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index a886c4369..14780ca8c 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -6464,19 +6464,18 @@ class basic_json } static constexpr auto d = - std::numeric_limits::digits10+1; + std::numeric_limits::digits10; + static_assert(d == 6 or d == 15 or d == 16 or d == 17, ""); - // I'm not sure why we need that +1 above, if at all, + static constexpr auto fmt = d == 6 ? "%.7g" + : d == 15 ? "%.16g" + : d == 16 ? "%.17g" + : d == 17 ? "%.18g" + : "%.19g"; + // I'm not sure why we need to +1 the precision, // 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; diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index dd7af5dde..cac41603e 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -6464,19 +6464,18 @@ class basic_json } static constexpr auto d = - std::numeric_limits::digits10+1; + std::numeric_limits::digits10; + static_assert(d == 6 or d == 15 or d == 16 or d == 17, ""); - // I'm not sure why we need that +1 above, if at all, + static constexpr auto fmt = d == 6 ? "%.7g" + : d == 15 ? "%.16g" + : d == 16 ? "%.17g" + : d == 17 ? "%.18g" + : "%.19g"; + // I'm not sure why we need to +1 the precision, // 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; From 82b82fd48709429d7e87a72e4c11d1f290bf5752 Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Mon, 5 Dec 2016 20:33:28 -0500 Subject: [PATCH 4/9] Addressing msvc-specific compilation issues. --- src/json.hpp | 2 +- src/json.hpp.re2c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 14780ca8c..e0d94f776 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -6429,7 +6429,7 @@ class basic_json while(x and i < s_capacity) { - m_buf[i++] = '0' + abs(x % 10); + m_buf[i++] = static_cast('0' + std::labs(x % 10)); x /= 10; } diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index cac41603e..87977da66 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -6429,7 +6429,7 @@ class basic_json while(x and i < s_capacity) { - m_buf[i++] = '0' + abs(x % 10); + m_buf[i++] = static_cast('0' + std::labs(x % 10)); x /= 10; } From 738d4629554eae1870ea4f79308b22d12107c83f Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Tue, 6 Dec 2016 00:23:58 -0500 Subject: [PATCH 5/9] Bugfix: when working with C formatting functions we need to query C locales (localeconv) rather than std::locale --- src/json.hpp | 59 +++++++++++++++++++++++++++++------------------ src/json.hpp.re2c | 59 +++++++++++++++++++++++++++++------------------ 2 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index e0d94f776..876c35b6b 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -6419,11 +6419,14 @@ class basic_json private: static constexpr size_t s_capacity = 30; - std::array m_buf{}; + std::array m_buf{}; // +2 for leading '-' + // and trailing '\0' template void x_write(T x, std::true_type) { + static_assert(std::numeric_limits::digits10 <= s_capacity, ""); + const bool is_neg = x < 0; size_t i = 0; @@ -6433,12 +6436,7 @@ class basic_json x /= 10; } - if(i == s_capacity) - { - std::runtime_error( - "Number is unexpectedly long: " - + std::to_string(x)); - } + assert(i < s_capacity); if(i == 0) { @@ -6478,34 +6476,49 @@ class basic_json snprintf(m_buf.data(), m_buf.size(), fmt, x); +#if 0 + // C locales and C++ locales are similar but + // different. + // + // If working with C++ streams we'd've used + // these, but for C formatting functions we + // have to use C locales (setlocale / localeconv), + // rather than C++ locales (std::locale installed + // by std::locale::global()). const std::locale loc; - // erase thousands separator - { - const char sep = - std::use_facet< std::numpunct >( - loc).thousands_sep(); + const char thousands_sep = + std::use_facet< std::numpunct >( + loc).thousands_sep(); + const char decimal_point = + std::use_facet< std::numpunct >( + loc).decimal_point(); +#else + const auto loc = localeconv(); + assert(loc != nullptr); + const char thousands_sep = !loc->thousands_sep ? '\0' + : loc->thousands_sep[0]; + + const char decimal_point = !loc->decimal_point ? '\0' + : loc->decimal_point[0]; +#endif + + // erase thousands separator + if (thousands_sep) { auto end = std::remove(m_buf.begin(), m_buf.end(), - sep); + thousands_sep); std::fill(end, m_buf.end(), '\0'); } // convert decimal point to '.' + if (decimal_point and decimal_point != '.') { - const char decimal_point = - std::use_facet< std::numpunct >( - loc).decimal_point(); - - for(auto& c : m_buf) + for (auto& c : m_buf) { - if(decimal_point == '.') { - break; - } - - if(c == decimal_point) + if (c == decimal_point) { c = '.'; break; diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 87977da66..55574beea 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -6419,11 +6419,14 @@ class basic_json private: static constexpr size_t s_capacity = 30; - std::array m_buf{}; + std::array m_buf{}; // +2 for leading '-' + // and trailing '\0' template void x_write(T x, std::true_type) { + static_assert(std::numeric_limits::digits10 <= s_capacity, ""); + const bool is_neg = x < 0; size_t i = 0; @@ -6433,12 +6436,7 @@ class basic_json x /= 10; } - if(i == s_capacity) - { - std::runtime_error( - "Number is unexpectedly long: " - + std::to_string(x)); - } + assert(i < s_capacity); if(i == 0) { @@ -6478,34 +6476,49 @@ class basic_json snprintf(m_buf.data(), m_buf.size(), fmt, x); +#if 0 + // C locales and C++ locales are similar but + // different. + // + // If working with C++ streams we'd've used + // these, but for C formatting functions we + // have to use C locales (setlocale / localeconv), + // rather than C++ locales (std::locale installed + // by std::locale::global()). const std::locale loc; - // erase thousands separator - { - const char sep = - std::use_facet< std::numpunct >( - loc).thousands_sep(); + const char thousands_sep = + std::use_facet< std::numpunct >( + loc).thousands_sep(); + const char decimal_point = + std::use_facet< std::numpunct >( + loc).decimal_point(); +#else + const auto loc = localeconv(); + assert(loc != nullptr); + const char thousands_sep = !loc->thousands_sep ? '\0' + : loc->thousands_sep[0]; + + const char decimal_point = !loc->decimal_point ? '\0' + : loc->decimal_point[0]; +#endif + + // erase thousands separator + if (thousands_sep) { auto end = std::remove(m_buf.begin(), m_buf.end(), - sep); + thousands_sep); std::fill(end, m_buf.end(), '\0'); } // convert decimal point to '.' + if (decimal_point and decimal_point != '.') { - const char decimal_point = - std::use_facet< std::numpunct >( - loc).decimal_point(); - - for(auto& c : m_buf) + for (auto& c : m_buf) { - if(decimal_point == '.') { - break; - } - - if(c == decimal_point) + if (c == decimal_point) { c = '.'; break; From 50f0484ad532635ee362da9406b9f3948a3ee10c Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Wed, 7 Dec 2016 20:23:25 -0500 Subject: [PATCH 6/9] Added unit test for issue #378 --- test/src/unit-regression.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index ada1e1042..3e303fbb0 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -401,6 +401,41 @@ TEST_CASE("regression tests") //CHECK(j3c.dump() == "1e04"); // roundtrip error } + SECTION("issue #378 - locale-independent num-to-str") + { + setlocale(LC_NUMERIC, ""); + + auto loc = localeconv(); + auto orig_decimal_point = loc->decimal_point; + auto orig_thousands_sep = loc->thousands_sep; + char comma[] = ","; + char thsep[] = "'"; + + loc->decimal_point = comma; + loc->thousands_sep = thsep; + + // verify that snprintf uses special decimal and grouping characters + { + std::array buf; + std::snprintf(buf.data(), buf.size(), "%.2f", 12345.67); + CHECK(strcmp(buf.data(), "12345,67") == 0); + + buf.fill(0); + std::snprintf(buf.data(), buf.size(), "%'d", 1234567); + CHECK(strcmp(buf.data(), "1'234'567") == 0); + } + + // verify that dumped correctly with '.' and no grouping + const json j1 = 12345.67; + CHECK(j1.dump() == "12345.67"); + + const json j2 = 1234567; + CHECK(j2.dump() == "1234567"); + + loc->decimal_point = orig_decimal_point; + loc->thousands_sep = orig_thousands_sep; + } + SECTION("issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator") { json source = {"a", "b", "c"}; From 343c9f9baa7bc210bd0984fa56760b8f3092d2d4 Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Thu, 8 Dec 2016 22:36:18 -0500 Subject: [PATCH 7/9] Addressing compiler warnings --- src/json.hpp | 8 ++++---- src/json.hpp.re2c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 876c35b6b..bd172559e 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -6419,9 +6419,8 @@ class basic_json private: static constexpr size_t s_capacity = 30; - std::array m_buf{}; // +2 for leading '-' - // and trailing '\0' - + std::array m_buf{{}}; // +2 for leading '-' + // and trailing '\0' template void x_write(T x, std::true_type) { @@ -6432,7 +6431,8 @@ class basic_json while(x and i < s_capacity) { - m_buf[i++] = static_cast('0' + std::labs(x % 10)); + const auto digit = std::labs(static_cast(x % 10)); + m_buf[i++] = static_cast('0' + digit); x /= 10; } diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 55574beea..4411c64a9 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -6419,9 +6419,8 @@ class basic_json private: static constexpr size_t s_capacity = 30; - std::array m_buf{}; // +2 for leading '-' - // and trailing '\0' - + std::array m_buf{{}}; // +2 for leading '-' + // and trailing '\0' template void x_write(T x, std::true_type) { @@ -6432,7 +6431,8 @@ class basic_json while(x and i < s_capacity) { - m_buf[i++] = static_cast('0' + std::labs(x % 10)); + const auto digit = std::labs(static_cast(x % 10)); + m_buf[i++] = static_cast('0' + digit); x /= 10; } From 01930357f7bd52672bffbb3aef76f241d03e4906 Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Thu, 8 Dec 2016 22:39:38 -0500 Subject: [PATCH 8/9] Tweaking unit test, as digits grouping is failing to be invoked in CI --- test/src/unit-regression.cpp | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index 3e303fbb0..c4b87399a 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -403,37 +403,19 @@ TEST_CASE("regression tests") SECTION("issue #378 - locale-independent num-to-str") { - setlocale(LC_NUMERIC, ""); - - auto loc = localeconv(); - auto orig_decimal_point = loc->decimal_point; - auto orig_thousands_sep = loc->thousands_sep; - char comma[] = ","; - char thsep[] = "'"; - - loc->decimal_point = comma; - loc->thousands_sep = thsep; + setlocale(LC_NUMERIC, "de_DE.UTF-8"); // verify that snprintf uses special decimal and grouping characters { std::array buf; std::snprintf(buf.data(), buf.size(), "%.2f", 12345.67); CHECK(strcmp(buf.data(), "12345,67") == 0); - - buf.fill(0); - std::snprintf(buf.data(), buf.size(), "%'d", 1234567); - CHECK(strcmp(buf.data(), "1'234'567") == 0); } // verify that dumped correctly with '.' and no grouping const json j1 = 12345.67; - CHECK(j1.dump() == "12345.67"); - - const json j2 = 1234567; - CHECK(j2.dump() == "1234567"); - - loc->decimal_point = orig_decimal_point; - loc->thousands_sep = orig_thousands_sep; + CHECK(json(12345.67).dump() == "12345.67"); + setlocale(LC_NUMERIC, "C"); } SECTION("issue #233 - Can't use basic_json::iterator as a base iterator for std::move_iterator") From 65b9b0c429b6ecd1d255fd8dde5decc7fec0120f Mon Sep 17 00:00:00 2001 From: Alex Astashyn Date: Mon, 12 Dec 2016 19:50:21 -0500 Subject: [PATCH 9/9] Disabling snprintf pre-check, since can't get locale-specific behavior to manifest in AppVeyor --- test/src/unit-regression.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index c4b87399a..2763114a8 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -405,12 +405,15 @@ TEST_CASE("regression tests") { setlocale(LC_NUMERIC, "de_DE.UTF-8"); - // verify that snprintf uses special decimal and grouping characters + // Verify that snprintf uses special decimal and grouping characters. + // Disabled, because can't trigger locale-specific behavior in AppVeyor +#if 0 { std::array buf; std::snprintf(buf.data(), buf.size(), "%.2f", 12345.67); CHECK(strcmp(buf.data(), "12345,67") == 0); } +#endif // verify that dumped correctly with '.' and no grouping const json j1 = 12345.67;