From d22193a8b60ffef05d2a249f1bf2244f97cfc064 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 8 Oct 2026 16:33:32 +0200 Subject: [PATCH] Fix CI findings in the Zmij writer and its tests - bit_ops.hpp: include macro_scope.hpp (JSON_HEDLEY_ALWAYS_INLINE); fixes IWYU - to_chars.hpp: C4100 for the unused parameter in release builds, clang-tidy sign comparison, cpplint runtime/int, GCC -Wstrict-overflow (unsigned abs) - unit-to_chars.cpp: parse with the library instead of strtod (MinGW's strtod rounds some 16/17 digit inputs wrongly); no floating-point std::to_chars with icpc Signed-off-by: Niels Lohmann --- include/nlohmann/detail/bit_ops.hpp | 2 +- .../nlohmann/detail/conversions/to_chars.hpp | 9 +++++---- single_include/nlohmann/json.hpp | 13 +++++++------ tests/src/unit-to_chars.cpp | 17 ++++++++++++++--- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/include/nlohmann/detail/bit_ops.hpp b/include/nlohmann/detail/bit_ops.hpp index 9f86ce3a5..bf6d797ec 100644 --- a/include/nlohmann/detail/bit_ops.hpp +++ b/include/nlohmann/detail/bit_ops.hpp @@ -10,7 +10,7 @@ #include // uint64_t -#include +#include // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN // Portable bit-level helpers for the number and string scanners. They use // compiler builtins where available and plain C++ otherwise, so they need no diff --git a/include/nlohmann/detail/conversions/to_chars.hpp b/include/nlohmann/detail/conversions/to_chars.hpp index 837e8d588..0e16152c3 100644 --- a/include/nlohmann/detail/conversions/to_chars.hpp +++ b/include/nlohmann/detail/conversions/to_chars.hpp @@ -1258,7 +1258,7 @@ inline char* write_decimal(char* first, std::uint64_t digits, int exp) noexcept first[1] = '.'; char* const end = first + (k == 1 ? 1 : k + 1); const int e = n - 1; - const auto ea = static_cast(e < 0 ? -e : e); + const auto ea = e < 0 ? 0u - static_cast(e) : static_cast(e); // (unsigned: no signed overflow to assume) const bool three = ea >= 100; end[0] = 'e'; end[1] = e < 0 ? '-' : '+'; @@ -1298,7 +1298,7 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep // NOLINTBEGIN(portability-simd-intrinsics) // the two halves in the 64-bit lanes, each as abcd * 2^32 + efgh, then as // bytes (as eight_digit_bytes(), one lane each) - const __m128i x = _mm_set_epi64x(static_cast(sig - (upper * 100000000u)), static_cast(upper)); + const __m128i x = _mm_set_epi64x(static_cast(sig - (upper * 100000000u)), static_cast(upper)); // NOLINT(runtime/int) const __m128i abcd = _mm_srli_epi64(_mm_mul_epu32(x, _mm_set1_epi64x(109951163)), 40); // 2^40 / 10000 + 1 const __m128i abcd_efgh = _mm_add_epi64(x, _mm_mul_epu32(abcd, _mm_set1_epi64x(4294957296))); // 2^32 - 10000 // 32-bit lanes in the order of the text: abcd, efgh of both halves @@ -1413,7 +1413,7 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep first[1] = '.'; char* const end = first + (len == 1 ? 1 : len + 1); const int e = n - 1; - const auto ea = static_cast(e < 0 ? -e : e); + const auto ea = e < 0 ? 0u - static_cast(e) : static_cast(e); // (unsigned: no signed overflow to assume) const bool three = ea >= 100; end[0] = 'e'; end[1] = e < 0 ? '-' : '+'; @@ -1472,6 +1472,7 @@ JSON_HEDLEY_RETURNS_NON_NULL char* write_positive(char* first, const char* last, FloatType value) { JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); + static_cast(last); // (only used in the assertion) // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted @@ -1513,7 +1514,7 @@ inline char* write_positive(char* first, const char* last, double value) } std::array buf; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read const auto len = static_cast(write_shortest(buf.data(), d) - buf.data()); - JSON_ASSERT(static_cast(last - first) >= len); + JSON_ASSERT(last - first >= static_cast(len)); std::memcpy(first, buf.data(), len); return first + len; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6bffc8132..4faec4138 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8794,8 +8794,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // uint64_t -// #include - +// #include +// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN // Portable bit-level helpers for the number and string scanners. They use // compiler builtins where available and plain C++ otherwise, so they need no @@ -26570,7 +26570,7 @@ inline char* write_decimal(char* first, std::uint64_t digits, int exp) noexcept first[1] = '.'; char* const end = first + (k == 1 ? 1 : k + 1); const int e = n - 1; - const auto ea = static_cast(e < 0 ? -e : e); + const auto ea = e < 0 ? 0u - static_cast(e) : static_cast(e); // (unsigned: no signed overflow to assume) const bool three = ea >= 100; end[0] = 'e'; end[1] = e < 0 ? '-' : '+'; @@ -26610,7 +26610,7 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep // NOLINTBEGIN(portability-simd-intrinsics) // the two halves in the 64-bit lanes, each as abcd * 2^32 + efgh, then as // bytes (as eight_digit_bytes(), one lane each) - const __m128i x = _mm_set_epi64x(static_cast(sig - (upper * 100000000u)), static_cast(upper)); + const __m128i x = _mm_set_epi64x(static_cast(sig - (upper * 100000000u)), static_cast(upper)); // NOLINT(runtime/int) const __m128i abcd = _mm_srli_epi64(_mm_mul_epu32(x, _mm_set1_epi64x(109951163)), 40); // 2^40 / 10000 + 1 const __m128i abcd_efgh = _mm_add_epi64(x, _mm_mul_epu32(abcd, _mm_set1_epi64x(4294957296))); // 2^32 - 10000 // 32-bit lanes in the order of the text: abcd, efgh of both halves @@ -26725,7 +26725,7 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep first[1] = '.'; char* const end = first + (len == 1 ? 1 : len + 1); const int e = n - 1; - const auto ea = static_cast(e < 0 ? -e : e); + const auto ea = e < 0 ? 0u - static_cast(e) : static_cast(e); // (unsigned: no signed overflow to assume) const bool three = ea >= 100; end[0] = 'e'; end[1] = e < 0 ? '-' : '+'; @@ -26784,6 +26784,7 @@ JSON_HEDLEY_RETURNS_NON_NULL char* write_positive(char* first, const char* last, FloatType value) { JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); + static_cast(last); // (only used in the assertion) // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted @@ -26825,7 +26826,7 @@ inline char* write_positive(char* first, const char* last, double value) } std::array buf; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read const auto len = static_cast(write_shortest(buf.data(), d) - buf.data()); - JSON_ASSERT(static_cast(last - first) >= len); + JSON_ASSERT(last - first >= static_cast(len)); std::memcpy(first, buf.data(), len); return first + len; } diff --git a/tests/src/unit-to_chars.cpp b/tests/src/unit-to_chars.cpp index 2745f7f95..8ced43ad0 100644 --- a/tests/src/unit-to_chars.cpp +++ b/tests/src/unit-to_chars.cpp @@ -639,12 +639,22 @@ std::pair digits_and_exponent(const std::string& s) return {digits, e}; } +/// the correctly rounded double of a decimal text +/// (not std::strtod: the C runtimes of some platforms, e.g. MinGW's, round +/// some 16 and 17 digit inputs wrongly) +double parse_double(const std::string& text) +{ + const nlohmann::json j = nlohmann::json::parse(text, nullptr, false); + // (a discarded value: out of range, as strtod's HUGE_VAL) + return j.is_discarded() ? std::numeric_limits::infinity() : j.get(); +} + /// whether the decimal digits * 10^e reads back as v bool reads_back(const std::string& digits, int e, double v) { const std::string text = digits + "e" + std::to_string(e); // (compared bit for bit: v is positive and finite, and -Wfloat-equal) - return reinterpret_bits(std::strtod(text.c_str(), nullptr)) == reinterpret_bits(v); + return reinterpret_bits(parse_double(text)) == reinterpret_bits(v); } /// Check the representation of a positive finite double: it reads back as @@ -655,7 +665,7 @@ void check_shortest(double v) char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, v); const std::string text(buf.data(), end); CAPTURE(text) - CHECK(std::strtod(text.c_str(), nullptr) == v); + CHECK(parse_double(text) == v); // the layout is that of format_buffer() for the same digits std::array reference{}; int len = 0; @@ -691,7 +701,8 @@ void check_shortest(double v) CHECK(!reads_back(std::to_string(candidate), e, v)); } } -#if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars) + // (icpc with libstdc++ 11 defines __cpp_lib_to_chars, but has no floating-point std::to_chars) +#if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars) && !defined(__INTEL_COMPILER) // the closest of the shortest representations, as std::to_chars finds it std::array std_text{}; const auto r = std::to_chars(std_text.data(), std_text.data() + std_text.size(), v, std::chars_format::scientific);