From 98cfd55317c5263d8e2536e2ba5acaf9540b3e0b Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 2 Nov 2026 09:32:50 +0100 Subject: [PATCH] Move the fraction only for a multibyte decimal point For a one-byte decimal point, the std::copy() call had its destination at the start of its source range, which std::copy() does not allow. The bytes are now moved only when the decimal point is longer than the '.' it is replaced with. Also name the right place of get_decimal_point(). Signed-off-by: Niels Lohmann --- include/nlohmann/detail/output/serializer.hpp | 19 ++++++++++++------- single_include/nlohmann/json.hpp | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 18643c8cc..33501dfc5 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -2096,8 +2096,9 @@ class serializer JSON_ASSERT(static_cast(len) < number_buffer.size()); // look up the locale's decimal point now, matching what - // snprintf_float() just used (see lexer::get_decimal_point()); it is a - // string, which can be longer than one byte (U+066B in ps_AF, #5993). + // snprintf_float() just used (see get_decimal_point() in + // number_parse.hpp); it is a string, which can be longer than one + // byte (U+066B in ps_AF, #5993). // A thousands separator needs no handling: %g never groups digits. const auto* loc = std::localeconv(); JSON_ASSERT(loc != nullptr); @@ -2114,11 +2115,15 @@ class serializer if (dec_pos != last) { *dec_pos = '.'; - // move the fraction and exponent behind the '.' (a no-op for a one-byte decimal point) - // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081 - const auto end = std::copy(dec_pos + static_cast(decimal_point_len), last, dec_pos + 1); - *end = '\0'; - len = end - first; + if (decimal_point_len > 1) + { + // move the fraction and exponent behind the '.' (std::copy + // allows the overlap, as the destination is before the source) + // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081 + const auto end = std::copy(dec_pos + static_cast(decimal_point_len), last, dec_pos + 1); + *end = '\0'; + len = end - first; + } } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 60c0643c6..23f6f616d 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -37343,8 +37343,9 @@ class serializer JSON_ASSERT(static_cast(len) < number_buffer.size()); // look up the locale's decimal point now, matching what - // snprintf_float() just used (see lexer::get_decimal_point()); it is a - // string, which can be longer than one byte (U+066B in ps_AF, #5993). + // snprintf_float() just used (see get_decimal_point() in + // number_parse.hpp); it is a string, which can be longer than one + // byte (U+066B in ps_AF, #5993). // A thousands separator needs no handling: %g never groups digits. const auto* loc = std::localeconv(); JSON_ASSERT(loc != nullptr); @@ -37361,11 +37362,15 @@ class serializer if (dec_pos != last) { *dec_pos = '.'; - // move the fraction and exponent behind the '.' (a no-op for a one-byte decimal point) - // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081 - const auto end = std::copy(dec_pos + static_cast(decimal_point_len), last, dec_pos + 1); - *end = '\0'; - len = end - first; + if (decimal_point_len > 1) + { + // move the fraction and exponent behind the '.' (std::copy + // allows the overlap, as the destination is before the source) + // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081 + const auto end = std::copy(dec_pos + static_cast(decimal_point_len), last, dec_pos + 1); + *end = '\0'; + len = end - first; + } } }