Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions include/fast_float/ascii_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -781,9 +781,28 @@ parse_int_string(UC const *p, UC const *pend, T &value,
}
// this check can be eliminated for all other types, but they will all require
// a max_digits(base) equivalent
if (digit_count == max_digits && i < min_safe_u64(base)) {
answer.ec = std::errc::result_out_of_range;
return answer;
if (digit_count == max_digits) {
// A value that wrapped below the smallest max_digits-length value has
// certainly overflowed.
if (i < min_safe_u64(base)) {
answer.ec = std::errc::result_out_of_range;
return answer;
}
// i >= min_safe_u64(base) is still not proof that it fits: for any base
// whose max_digits-length range exceeds 2^64 (base 10 reaches ~5.4 * 2^64
// at 20 digits) the accumulator can wrap a whole multiple of 2^64 and land
// back above min_safe, so the test above lets that overflow through. Re-run
// the parsed digits with a checked multiply-add to decide exactly.
uint64_t overflow_check = 0;
for (UC const *q = start_digits; q != p; ++q) {
uint8_t const digit = ch_to_digit(*q);
if (overflow_check >
(std::numeric_limits<uint64_t>::max() - digit) / uint64_t(base)) {
answer.ec = std::errc::result_out_of_range;
return answer;
}
overflow_check = uint64_t(base) * overflow_check + digit;
}
}

// check other types overflow
Expand Down
55 changes: 55 additions & 0 deletions tests/fast_int.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,61 @@ int main() {
++base_unsigned;
}

// unsigned out of range error base test, multi-wrap (64 bit)
// These values overflow uint64_t, but the accumulator wraps a whole multiple
// of 2^64 and lands back at or above the smallest max_digits-length value, so
// a single comparison against that bound does not catch the overflow. Bases
// 2, 4 and 16 are excluded because their max_digits-length range fits within
// a single 2^64 span.
std::vector<int> const unsigned_multiwrap_base{
3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36};
std::vector<std::string_view> const unsigned_multiwrap_base_test{
"22222222222222222222222222222222222222222",
"4400000000000000000000000000",
"5555555555555555555555555",
"66666666666666666666666",
"7777777777777777777777",
"888888888888888888888",
"46893488147419103233",
"AAAAAAAAAAAAAAAAAAA",
"BBBBBBBBBBBBBBBBBB",
"427772311192C9BAAB",
"DDDDDDDDDDDDDDDDD",
"532C82996D3A44919",
"GGGGGGGGGGGGGGGG",
"HHHHHHHHHHHHHHHH",
"3835GEGDF36622EG",
"JJJJJJJJJJJJJJJ",
"KKKKKKKKKKKKKKK",
"LLLLLLLLLLLLLLL",
"444BGHB4EG5DA2D",
"NNNNNNNNNNNNNN",
"JE5H4MNDLJGNLO",
"PPPPPPPPPPPPPP",
"QQQQQQQQQQQQQQ",
"RRRRRRRRRRRRRR",
"4H7QS52310IHQK",
"TTTTTTTTTTTTTT",
"UUUUUUUUUUUUU",
"VVVVVVVVVVVVV",
"WWWWWWWWWWWWW",
"XXXXXXXXXXXXX",
"YYYYYYYYYYYYY",
"6U831JL976P6O"};

for (std::size_t i = 0; i < unsigned_multiwrap_base_test.size(); ++i) {
auto const &f = unsigned_multiwrap_base_test[i];
uint64_t result;
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result,
unsigned_multiwrap_base[i]);
if (answer.ec != std::errc::result_out_of_range) {
std::cerr << "expected error for should be 'result_out_of_range': \"" << f
<< "\"" << std::endl;
return EXIT_FAILURE;
}
}

// just within range base test (64 bit)
std::vector<std::string_view> const int_within_range_base_test{
"111111111111111111111111111111111111111111111111111111111111111",
Expand Down
Loading