@@ -781,9 +781,27 @@ parse_int_string(UC const *p, UC const *pend, T &value,
781781 }
782782 // this check can be eliminated for all other types, but they will all require
783783 // a max_digits(base) equivalent
784- if (digit_count == max_digits && i < min_safe_u64 (base)) {
785- answer.ec = std::errc::result_out_of_range;
786- return answer;
784+ if (digit_count == max_digits) {
785+ // At the max_digits boundary the accumulator `i` may have wrapped around
786+ // 2^64. A plain `i < min_safe_u64(base)` test is not sufficient: for any
787+ // base whose max_digits-length range exceeds 2^64 (base 10 reaches
788+ // ~5.4 * 2^64 at 20 digits) the value can wrap a whole multiple of 2^64 and
789+ // land back above min_safe, slipping through. Decide exactly in O(1) using
790+ // the leading digit, following the approach used in simdjson:
791+ // ms == min_safe_u64(base) == base^(max_digits-1), the smallest
792+ // max_digits-length value.
793+ // dmax == the largest leading digit whose number can still fit in u64.
794+ // The leading-digit band [d*ms, (d+1)*ms) has width ms < 2^64, so within
795+ // the single band where d == dmax the value straddles 2^64 at most once,
796+ // and a single threshold separates wrapped from non-wrapped values. A
797+ // leading digit above dmax always overflows; below dmax always fits.
798+ uint64_t const ms = min_safe_u64 (base);
799+ uint64_t const dmax = (std::numeric_limits<uint64_t >::max)() / ms;
800+ uint64_t const lead = ch_to_digit (*start_digits);
801+ if (lead > dmax || (lead == dmax && i < dmax * ms)) {
802+ answer.ec = std::errc::result_out_of_range;
803+ return answer;
804+ }
787805 }
788806
789807 // check other types overflow
0 commit comments