From 9dc5ba1d22a46dc1c91a3436d87d6a8f2cc1bae9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Oct 2026 09:21:34 +0200 Subject: [PATCH] Fix AppVeyor failures from the Zmij double writer MSVC 2015 warns C4554 on the shifts with casts in the fractional part of zmij::to_shortest despite the parentheses; with /WX every target fails to build. Name the shift amounts as constants instead. The shortest-digits test built the candidates with one digit fewer via a stream with std::setprecision(digits.size() - 2); the standard library of MSVC 2017 ignores a precision of 0, so for two-digit values the "shorter" candidates were the value itself. Truncate the digits instead: the value lies within half a unit of the last digit, so the truncated digits and their successor are the closest candidates. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/conversions/zmij.hpp | 5 +++- single_include/nlohmann/json.hpp | 5 +++- tests/src/unit-to_chars.cpp | 24 ++++++-------------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/include/nlohmann/detail/conversions/zmij.hpp b/include/nlohmann/detail/conversions/zmij.hpp index 40e477f7b..3820d891a 100644 --- a/include/nlohmann/detail/conversions/zmij.hpp +++ b/include/nlohmann/detail/conversions/zmij.hpp @@ -184,7 +184,10 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc const uint128_parts p10 = pow10(-dec_exp - 1); const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast(shift)); std::uint64_t integral = p.high >> static_cast(extra_shift); - const std::uint64_t fractional = (p.high << static_cast(64 - extra_shift)) | (p.low >> static_cast(extra_shift)); + // (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses) + constexpr unsigned fractional_low_shift = static_cast(extra_shift); + constexpr unsigned fractional_high_shift = 64u - fractional_low_shift; + const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift); std::uint64_t digit = 0; bool round_up = false; bool round_down = false; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a35f9e32c..a5e59b4e1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -25874,7 +25874,10 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc const uint128_parts p10 = pow10(-dec_exp - 1); const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast(shift)); std::uint64_t integral = p.high >> static_cast(extra_shift); - const std::uint64_t fractional = (p.high << static_cast(64 - extra_shift)) | (p.low >> static_cast(extra_shift)); + // (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses) + constexpr unsigned fractional_low_shift = static_cast(extra_shift); + constexpr unsigned fractional_high_shift = 64u - fractional_low_shift; + const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift); std::uint64_t digit = 0; bool round_up = false; bool round_down = false; diff --git a/tests/src/unit-to_chars.cpp b/tests/src/unit-to_chars.cpp index c9b72fc97..95c5f2bd0 100644 --- a/tests/src/unit-to_chars.cpp +++ b/tests/src/unit-to_chars.cpp @@ -21,11 +21,8 @@ using nlohmann::detail::dtoa_impl::reinterpret_bits; #include #include #include -#include #include -#include #include -#include #include #include #include @@ -689,20 +686,13 @@ void check_shortest(double v) const std::string& digits = de.first; if (digits.size() > 1) { - // the decimals of one digit fewer next to the value - // (a stream rather than snprintf("%.*e"), whose output GCC cannot bound) - std::ostringstream shorter; - shorter.imbue(std::locale::classic()); - shorter << std::scientific << std::setprecision(static_cast(digits.size()) - 2) << v; - const auto near = digits_and_exponent(shorter.str()); - // as an integer with digits.size() - 1 digits - std::string m = near.first; - int e = near.second; - while (m.size() < digits.size() - 1) - { - m += '0'; - --e; - } + // the decimals of one digit fewer next to the value: v lies within + // half a unit of the last digit of text, so the closest ones are the + // truncated digits and their successor + // (not a stream with std::setprecision(digits.size() - 2): the + // standard library of MSVC 2017 ignores a precision of 0) + const std::string m = digits.substr(0, digits.size() - 1); + const int e = de.second + 1; const std::uint64_t mid = std::stoull(m); for (const std::uint64_t candidate : {