From 26e5f4629befd392d34747c60c5d7c89ab8037b8 Mon Sep 17 00:00:00 2001 From: mrabine Date: Sat, 24 Oct 2026 21:55:57 +0200 Subject: [PATCH 1/2] Fix strtod rounding --- data/include/join/json.hpp | 24 +++++++++++++------- data/tests/json_reader_test.cpp | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/data/include/join/json.hpp b/data/include/join/json.hpp index 32555ae6..3d0daeff 100644 --- a/data/include/join/json.hpp +++ b/data/include/join/json.hpp @@ -1424,18 +1424,22 @@ namespace join value = static_cast (significand); - if (JOIN_UNLIKELY ((exponent > 22) && (exponent < (22 + 16)))) - { - value *= pow10[exponent - 22]; - exponent = 22; - } - if (JOIN_LIKELY ((exponent >= -22) && (exponent <= 22) && (value <= 9007199254740991.0))) { value = (exponent < 0) ? (value / pow10[-exponent]) : (value * pow10[exponent]); return true; } + if (JOIN_UNLIKELY ((exponent > 22) && (exponent < (22 + 16)))) + { + const double scaled = value * pow10[exponent - 22]; + if (scaled <= 9007199254740991.0) + { + value = scaled * pow10[22]; + return true; + } + } + if (JOIN_UNLIKELY (value == 0.0)) { return true; @@ -1489,8 +1493,12 @@ namespace join uint64_t bits = (static_cast (exp) << 52) | mant; uint64_t frac = high & 0x7FF; - bool roundUp = ((frac > 0x400) | ((frac == 0x400) && ((middle != 0) || (mant & 1))) | - ((frac == 0x3FF) && ((middle != 0)))); + if (JOIN_UNLIKELY ((frac == 0x3FF) && ((middle >> 8) == (~0ULL >> 8)))) + { + return false; + } + + bool roundUp = (frac > 0x400) | ((frac == 0x400) && ((middle != 0) || (mant & 1))); bits += roundUp; std::memcpy (&value, &bits, sizeof (double)); diff --git a/data/tests/json_reader_test.cpp b/data/tests/json_reader_test.cpp index 64e17c0a..83ac42c0 100644 --- a/data/tests/json_reader_test.cpp +++ b/data/tests/json_reader_test.cpp @@ -1313,6 +1313,46 @@ TEST (JsonReader, dbl) ASSERT_FALSE (value.empty ()); ASSERT_TRUE (value[0].isDouble ()); EXPECT_EQ (value[0].getDouble (), 2.2250738585072014e-308); + + stream.clear (); + stream.str ("[57507.7647e38]"); + ASSERT_EQ (JsonReader ().deserialize (stream, document), 0) << join::lastError.message (); + ASSERT_TRUE (value.isArray ()); + ASSERT_FALSE (value.empty ()); + ASSERT_TRUE (value[0].isDouble ()); + EXPECT_EQ (value[0].getDouble (), 57507.7647e38); + + stream.clear (); + stream.str ("[0.759154e41]"); + ASSERT_EQ (JsonReader ().deserialize (stream, document), 0) << join::lastError.message (); + ASSERT_TRUE (value.isArray ()); + ASSERT_FALSE (value.empty ()); + ASSERT_TRUE (value[0].isDouble ()); + EXPECT_EQ (value[0].getDouble (), 0.759154e41); + + stream.clear (); + stream.str ("[1e126]"); + ASSERT_EQ (JsonReader ().deserialize (stream, document), 0) << join::lastError.message (); + ASSERT_TRUE (value.isArray ()); + ASSERT_FALSE (value.empty ()); + ASSERT_TRUE (value[0].isDouble ()); + EXPECT_EQ (value[0].getDouble (), 1e126); + + stream.clear (); + stream.str ("[2e126]"); + ASSERT_EQ (JsonReader ().deserialize (stream, document), 0) << join::lastError.message (); + ASSERT_TRUE (value.isArray ()); + ASSERT_FALSE (value.empty ()); + ASSERT_TRUE (value[0].isDouble ()); + EXPECT_EQ (value[0].getDouble (), 2e126); + + stream.clear (); + stream.str ("[9007199254740993.0]"); + ASSERT_EQ (JsonReader ().deserialize (stream, document), 0) << join::lastError.message (); + ASSERT_TRUE (value.isArray ()); + ASSERT_FALSE (value.empty ()); + ASSERT_TRUE (value[0].isDouble ()); + EXPECT_EQ (value[0].getDouble (), 9007199254740992.0); } /** From 7442a37784f84e49b7f1fb920d046da27020cb68 Mon Sep 17 00:00:00 2001 From: mrabine Date: Sat, 24 Oct 2026 22:29:18 +0200 Subject: [PATCH 2/2] Skip the low power product in strtodFast when rounding is unambiguous --- data/include/join/json.hpp | 81 +++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/data/include/join/json.hpp b/data/include/join/json.hpp index 3d0daeff..49376e64 100644 --- a/data/include/join/json.hpp +++ b/data/include/join/json.hpp @@ -1363,6 +1363,34 @@ namespace join : std::numeric_limits::quiet_NaN ()); } + /** + * @brief multiply two 64 bits unsigned integers. + * @param a multiplicand. + * @param b multiplier. + * @param high high 64 bits of the 128-bit result. + * @param low low 64 bits of the 128-bit result. + */ + inline void umul128 (uint64_t a, uint64_t b, uint64_t& high, uint64_t& low) noexcept + { +#if defined(__SIZEOF_INT128__) + __uint128_t p = static_cast<__uint128_t> (a) * b; + + high = static_cast (p >> 64); + low = static_cast (p); +#else + uint64_t b_lo = static_cast (b); + uint64_t b_hi = b >> 32; + uint64_t p0 = (a & 0xFFFFFFFF) * b_lo; + uint64_t p1 = (a >> 32) * b_lo; + uint64_t p2 = (a & 0xFFFFFFFF) * b_hi; + uint64_t p3 = (a >> 32) * b_hi; + uint64_t carry = (p0 >> 32) + (p1 & 0xFFFFFFFF) + (p2 & 0xFFFFFFFF); + + low = (carry << 32) | (p0 & 0xFFFFFFFF); + high = (carry >> 32) + (p1 >> 32) + (p2 >> 32) + p3; +#endif + } + /** * @brief multiply 192 bits unsigned integer by 64 bits unsigned integer. * @param hi high 64 bits of the 128-bit multiplicand. @@ -1375,39 +1403,12 @@ namespace join inline void umul192 (uint64_t hi, uint64_t lo, uint64_t significand, uint64_t& high, uint64_t& middle, uint64_t& low) noexcept { -#if defined(__SIZEOF_INT128__) - __uint128_t h = static_cast<__uint128_t> (hi) * significand; - __uint128_t l = static_cast<__uint128_t> (lo) * significand; - __uint128_t s = h + (l >> 64); + uint64_t hi_hi, hi_lo, lo_hi; + umul128 (hi, significand, hi_hi, hi_lo); + umul128 (lo, significand, lo_hi, low); - high = static_cast (s >> 64); - middle = static_cast (s); - low = static_cast (l); -#else - uint64_t hi_hi, hi_lo, lo_hi, lo_lo; - - uint64_t m_lo = static_cast (significand); - uint64_t m_hi = significand >> 32; - uint64_t p0 = (hi & 0xFFFFFFFF) * m_lo; - uint64_t p1 = (hi >> 32) * m_lo; - uint64_t p2 = (hi & 0xFFFFFFFF) * m_hi; - uint64_t p3 = (hi >> 32) * m_hi; - uint64_t carry = (p0 >> 32) + (p1 & 0xFFFFFFFF) + (p2 & 0xFFFFFFFF); - hi_lo = (carry << 32) | (p0 & 0xFFFFFFFF); - hi_hi = (carry >> 32) + (p1 >> 32) + (p2 >> 32) + p3; - - p0 = (lo & 0xFFFFFFFF) * m_lo; - p1 = (lo >> 32) * m_lo; - p2 = (lo & 0xFFFFFFFF) * m_hi; - p3 = (lo >> 32) * m_hi; - carry = (p0 >> 32) + (p1 & 0xFFFFFFFF) + (p2 & 0xFFFFFFFF); - lo_lo = (carry << 32) | (p0 & 0xFFFFFFFF); - lo_hi = (carry >> 32) + (p1 >> 32) + (p2 >> 32) + p3; - - low = lo_lo; middle = hi_lo + lo_hi; - high = hi_hi + (middle < hi_lo ? 1 : 0); -#endif + high = hi_hi + (middle < hi_lo); } /** @@ -1452,9 +1453,25 @@ namespace join uint64_t high, middle, low; const details::Power& power = details::atodpow[exponent + 325]; - umul192 (power.hi, power.lo, significand, high, middle, low); int64_t exp = ((exponent * 217706) >> 16) + 1087; + umul128 (power.hi, significand, high, middle); + if (JOIN_LIKELY (high != 0)) + { + const int shift = __builtin_clzll (high); + const uint64_t top = (shift != 0) ? ((high << shift) | (middle >> (64 - shift))) : high; + const uint64_t frac = top & 0x7FF; + + if (JOIN_LIKELY (((frac < 0x3FE) || (frac > 0x400)) && (exp - shift > 0) && (exp - shift < 2047))) + { + uint64_t bits = (static_cast (exp - shift) << 52) | ((top >> 11) & 0xFFFFFFFFFFFFF); + bits += (frac > 0x400); + std::memcpy (&value, &bits, sizeof (double)); + return true; + } + } + umul192 (power.hi, power.lo, significand, high, middle, low); + int lz; if (high != 0) {