Write integers of 17 to 20 digits eight digits at a time in dump()

#5634 made dump() write integers below 10^16 eight digits at a time; larger
ones still took the two-digits-per-division loop and count_digits(). Random
64-bit integers almost all have 17 to 20 digits (99.9 % of signed_ints.json
and unsigned_ints.json), so Dump/signed_ints and Dump/unsigned_ints gained
only 1.4x over 3.12.0, against 2.3x for small integers.

A 64-bit magnitude is now written as its first one to four digits and two
groups of eight. Wider magnitudes (__int128 and class types, #5805) keep the
loop: number types of at most 64 bits are selected by type, so no comparison
is reported as always true; for others the value is compared. The sign
handling of #1447/#5815 (remove_sign() into number_magnitude_t) is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-30 15:08:15 +01:00
1 parent ea8a05484b
commit a6ab196ad5
3 files changed
+114

No files matched your search

@@ -1977,6 +1977,23 @@ class serializer
return;
}
// 17 to 20 digits: the first one to four, then twice eight
if (magnitude_fits_64_bits(abs_value, std::integral_constant<bool, magnitude_has_at_most_64_bits>()))
{
const auto value = static_cast<std::uint64_t>(abs_value);
const std::uint64_t top = value / 10000000000000000u; // 1 to 1844
const std::uint64_t low16 = value - (top * 10000000000000000u);
const std::uint64_t middle = low16 / 100000000u;
const std::uint64_t first = dtoa_impl::eight_digit_bytes(top);
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
char* const p = buffer_ptr + n_chars;
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u);
write_buffer_pos += n_chars + 24 - leading;
return;
}
n_chars += count_digits(abs_value);
// spare 1 byte for '\0'
@@ -2122,6 +2139,23 @@ class serializer
}
}
/// whether every number_magnitude_t fits 64 bits
static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits<number_magnitude_t>::is_specialized
&& std::numeric_limits<number_magnitude_t>::digits <= 64;
/// whether @a x fits 64 bits: always if number_magnitude_t has at most
/// 64 bits (selected by type, as the comparison would be reported as
/// always true), else compared
static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept
{
return true;
}
static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept
{
return x <= 18446744073709551615u;
}
/*
* Helper function for dump_integer
*