#ifndef __cplusplus #error fastfloat requires a C++ compiler #endif // We want to enable the tests only for C++17 and above. #ifndef FASTFLOAT_CPLUSPLUS #if defined(_MSVC_LANG) && !defined(__clang__) #define FASTFLOAT_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG) #else #define FASTFLOAT_CPLUSPLUS __cplusplus #endif #endif #if FASTFLOAT_CPLUSPLUS >= 201703L #include #include #include #include #include #include #include #include #include "fast_float/fast_float.h" #include /* all tests conducted are to check fast_float::from_chars functionality with int and unsigned test cases include: int basic tests - numbers only, numbers with strings behind, decimals, negative numbers unsigned basic tests - numbers only, numbers with strings behind, decimals int invalid tests - strings only, strings with numbers behind, space in front of number, plus sign in front of number unsigned invalid tests - strings only, strings with numbers behind, space in front of number, plus/minus sign in front of number int out of range tests - numbers exceeding int bit size for 8, 16, 32, and 64 bits unsigned out of range tests - numbers exceeding unsigned bit size 8, 16, 32, and 64 bits int pointer tests - points to first character that is not recognized as int unsigned pointer tests - points to first character that is not recognized as unsigned int/unsigned base 2 tests - numbers are converted from binary to decimal octal tests - numbers are converted from octal to decimal hex tests - numbers are converted from hex to decimal (Note: 0x and 0X are considered invalid) invalid base tests - any base not within 2-36 is invalid out of range base tests - numbers exceeding int/unsigned bit size after converted from base (Note: only 64 bit int and unsigned are tested) within range base tests - max/min numbers are still within int/unsigned bit size after converted from base (Note: only 64 bit int and unsigned are tested) leading zeros tests - ignores all zeroes in front of valid number after converted from base */ int main() { // int basic test std::vector const int_basic_test_expected{0, 10, -40, 1001, 9}; std::vector const int_basic_test{"0", "10 ", "-40", "1001 with text", "9.999"}; for (std::size_t i = 0; i < int_basic_test.size(); ++i) { auto const f = int_basic_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { if (answer.ec == std::errc::invalid_argument) { std::cerr << "could not convert to int for input: \"" << f << "\" because of invalid argument" << std::endl; } else if (answer.ec == std::errc::result_out_of_range) { std::cerr << "could not convert to int for input: \"" << f << "\" because it's out of range" << std::endl; } else { std::cerr << "could not convert to int for input: \"" << f << "\" because of an unknown error" << std::endl; } return EXIT_FAILURE; } else if (result != int_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected int: " << int_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // unsigned basic test std::vector const unsigned_basic_test_expected{0, 10, 1001, 9}; std::vector const unsigned_basic_test{ "0", "10 ", "1001 with text", "9.999"}; for (std::size_t i = 0; i < unsigned_basic_test.size(); ++i) { auto const &f = unsigned_basic_test[i]; unsigned result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to unsigned for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != unsigned_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected unsigned: " << unsigned_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // char basic test std::vector const char_basic_test_expected{0, 10, 40, 100, 9}; std::vector const char_basic_test{"0", "10 ", "40", "100 with text", "9.999"}; for (std::size_t i = 0; i < char_basic_test.size(); ++i) { auto const f = char_basic_test[i]; char result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to char for input: \"" << f << "\" because of invalid argument" << std::endl; return EXIT_FAILURE; } else if (result != char_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected char: " << static_cast(char_basic_test_expected[i]) << std::endl; return EXIT_FAILURE; } } // short basic test std::vector const short_basic_test_expected{0, 10, -40, 1001, 9}; std::vector const short_basic_test{ "0", "10 ", "-40", "1001 with text", "9.999"}; for (std::size_t i = 0; i < short_basic_test.size(); ++i) { auto const f = short_basic_test[i]; short result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to short for input: \"" << f << "\" because of invalid argument" << std::endl; return EXIT_FAILURE; } else if (result != short_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected short: " << short_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // long basic test std::vector const long_basic_test_expected{0, 10, -40, 1001, 9}; std::vector const long_basic_test{ "0", "10 ", "-40", "1001 with text", "9.999"}; for (std::size_t i = 0; i < long_basic_test.size(); ++i) { auto const f = long_basic_test[i]; long result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to long for input: \"" << f << "\" because of invalid argument" << std::endl; return EXIT_FAILURE; } else if (result != long_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected long: " << long_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // long long basic test std::vector const long_long_basic_test_expected{0, 10, -40, 1001, 9}; std::vector const long_long_basic_test{ "0", "10 ", "-40", "1001 with text", "9.999"}; for (std::size_t i = 0; i < long_long_basic_test.size(); ++i) { auto const f = long_long_basic_test[i]; long long result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to long long for input: \"" << f << "\" because of invalid argument" << std::endl; return EXIT_FAILURE; } else if (result != long_long_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected long long: " << long_long_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // unsigned char basic test std::vector const unsigned_char_basic_test_expected{0, 10, 100, 9}; std::vector const unsigned_char_basic_test{ "0", "10 ", "100 with text", "9.999"}; for (std::size_t i = 0; i < unsigned_char_basic_test.size(); ++i) { auto const &f = unsigned_char_basic_test[i]; unsigned char result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to unsigned char for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != unsigned_char_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected unsigned char: " << static_cast(unsigned_char_basic_test_expected[i]) << std::endl; return EXIT_FAILURE; } } // unsigned short basic test std::vector const unsigned_short_basic_test_expected{0, 10, 1001, 9}; std::vector const unsigned_short_basic_test{ "0", "10 ", "1001 with text", "9.999"}; for (std::size_t i = 0; i < unsigned_short_basic_test.size(); ++i) { auto const &f = unsigned_short_basic_test[i]; unsigned short result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to unsigned short for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != unsigned_short_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected unsigned short: " << unsigned_short_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // unsigned long basic test std::vector const unsigned_long_basic_test_expected{0, 10, 1001, 9}; std::vector const unsigned_long_basic_test{ "0", "10 ", "1001 with text", "9.999"}; for (std::size_t i = 0; i < unsigned_long_basic_test.size(); ++i) { auto const &f = unsigned_long_basic_test[i]; unsigned long result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to unsigned long for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != unsigned_long_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected unsigned long: " << unsigned_long_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // unsigned long long basic test std::vector const unsigned_long_long_basic_test_expected{ 0, 10, 1001, 9}; std::vector const unsigned_long_long_basic_test{ "0", "10 ", "1001 with text", "9.999"}; for (std::size_t i = 0; i < unsigned_long_long_basic_test.size(); ++i) { auto const &f = unsigned_long_long_basic_test[i]; unsigned long long result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to unsigned long long for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != unsigned_long_long_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected unsigned long long: " << unsigned_long_long_basic_test_expected[i] << std::endl; return EXIT_FAILURE; } } // bool basic test std::vector const bool_basic_test_expected{false, true}; std::vector const bool_basic_test{"0", "1"}; for (std::size_t i = 0; i < bool_basic_test.size(); ++i) { auto const &f = bool_basic_test[i]; bool result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to bool for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != bool_basic_test_expected[i]) { std::cerr << "result \"" << f << "\" did not match with expected bool: " << (bool_basic_test_expected[i] ? "true" : "false") << std::endl; return EXIT_FAILURE; } } // int invalid error test std::vector const int_invalid_argument_test{ "text", "text with 1002", "+50", " 50"}; for (std::size_t i = 0; i < int_invalid_argument_test.size(); ++i) { auto const &f = int_invalid_argument_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc::invalid_argument) { std::cerr << "expected error should be 'invalid_argument' for: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } } // unsigned invalid error test std::vector const unsigned_invalid_argument_test{ "text", "text with 1002", "+50", " 50", "-50"}; for (std::size_t i = 0; i < unsigned_invalid_argument_test.size(); ++i) { auto const &f = unsigned_invalid_argument_test[i]; unsigned result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc::invalid_argument) { std::cerr << "expected error should be 'invalid_argument' for: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } } // int out of range error test #1 (8 bit) std::vector const int_out_of_range_test_1{ "2000000000000000000000", "128", "-129"}; for (std::size_t i = 0; i < int_out_of_range_test_1.size(); ++i) { auto const &f = int_out_of_range_test_1[i]; int8_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // int out of range error test #2 (16 bit) std::vector const int_out_of_range_test_2{ "2000000000000000000000", "32768", "-32769"}; for (std::size_t i = 0; i < int_out_of_range_test_2.size(); ++i) { auto const &f = int_out_of_range_test_2[i]; int16_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // int out of range error test #3 (32 bit) std::vector const int_out_of_range_test_3{ "2000000000000000000000", "2147483648", "-2147483649"}; for (std::size_t i = 0; i < int_out_of_range_test_3.size(); ++i) { auto const &f = int_out_of_range_test_3[i]; int32_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // int out of range error test #4 (64 bit) std::vector const int_out_of_range_test_4{ "2000000000000000000000", "9223372036854775808", "-9223372036854775809"}; for (std::size_t i = 0; i < int_out_of_range_test_4.size(); ++i) { auto const &f = int_out_of_range_test_4[i]; int64_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // unsigned out of range error test #1 (8 bit) std::vector const unsigned_out_of_range_test_1{ "2000000000000000000000", "256"}; for (std::size_t i = 0; i < unsigned_out_of_range_test_1.size(); ++i) { auto const &f = unsigned_out_of_range_test_1[i]; uint8_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // unsigned out of range error test #2 (16 bit) std::vector const unsigned_out_of_range_test_2{ "2000000000000000000000", "65536"}; for (std::size_t i = 0; i < unsigned_out_of_range_test_2.size(); ++i) { auto const &f = unsigned_out_of_range_test_2[i]; uint16_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // unsigned out of range error test #3 (32 bit) std::vector const unsigned_out_of_range_test_3{ "2000000000000000000000", "4294967296"}; for (std::size_t i = 0; i < unsigned_out_of_range_test_3.size(); ++i) { auto const &f = unsigned_out_of_range_test_3[i]; uint32_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // unsigned out of range error test #4 (64 bit) std::vector const unsigned_out_of_range_test_4{ "2000000000000000000000", "18446744073709551616"}; for (std::size_t i = 0; i < unsigned_out_of_range_test_4.size(); ++i) { auto const &f = unsigned_out_of_range_test_4[i]; uint64_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 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; } } // int pointer test #1 (only numbers) std::vector const int_pointer_test_1{"0", "010", "-40"}; for (std::size_t i = 0; i < int_pointer_test_1.size(); ++i) { auto const &f = int_pointer_test_1[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); if (answer.ec != std::errc()) { std::cerr << "could not convert to int for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (strcmp(answer.ptr, "") != 0) { std::cerr << "ptr of result " << f << " did not match with expected ptr: \"\"" << std::endl; return EXIT_FAILURE; } } // int pointer test #2 (string behind numbers) std::string_view const int_pointer_test_2 = "1001 with text"; auto const &f2 = int_pointer_test_2; int result2; auto answer2 = fast_float::from_chars(f2.data(), f2.data() + f2.size(), result2); if (strcmp(answer2.ptr, " with text") != 0) { std::cerr << "ptr of result " << f2 << " did not match with expected ptr: \"with text\"" << std::endl; return EXIT_FAILURE; } // int pointer test #3 (string with newline behind numbers) std::string_view const int_pointer_test_3 = "1001 with text\n"; auto const &f3 = int_pointer_test_3; int result3; auto answer3 = fast_float::from_chars(f3.data(), f3.data() + f3.size(), result3); if (strcmp(answer3.ptr, " with text\n") != 0) { std::cerr << "ptr of result " << f3 << " did not match with expected ptr: with text" << std::endl; return EXIT_FAILURE; } // int pointer test #4 (float) std::string_view const int_pointer_test_4 = "9.999"; auto const &f4 = int_pointer_test_4; int result4; auto answer4 = fast_float::from_chars(f4.data(), f4.data() + f4.size(), result4); if (strcmp(answer4.ptr, ".999") != 0) { std::cerr << "ptr of result " << f4 << " did not match with expected ptr: .999" << std::endl; return EXIT_FAILURE; } // int pointer test #5 (invalid int) std::string_view const int_pointer_test_5 = "+50"; auto const &f5 = int_pointer_test_5; int result5; auto answer5 = fast_float::from_chars(f5.data(), f5.data() + f5.size(), result5); if (strcmp(answer5.ptr, "+50") != 0) { std::cerr << "ptr of result " << f5 << " did not match with expected ptr: +50" << std::endl; return EXIT_FAILURE; } // unsigned pointer test #2 (string behind numbers) std::string_view const unsigned_pointer_test_1 = "1001 with text"; auto const &f6 = unsigned_pointer_test_1; unsigned result6; auto answer6 = fast_float::from_chars(f6.data(), f6.data() + f6.size(), result6); if (strcmp(answer6.ptr, " with text") != 0) { std::cerr << "ptr of result " << f6 << " did not match with expected ptr: with text" << std::endl; return EXIT_FAILURE; } // unsigned pointer test #2 (invalid unsigned) std::string_view const unsigned_pointer_test_2 = "-50"; auto const &f7 = unsigned_pointer_test_2; unsigned result7; auto answer7 = fast_float::from_chars(f7.data(), f7.data() + f7.size(), result7); if (strcmp(answer7.ptr, "-50") != 0) { std::cerr << "ptr of result " << f7 << " did not match with expected ptr: -50" << std::endl; return EXIT_FAILURE; } // int base 2 test std::vector const int_base_2_test_expected{0, 1, 4, 2, -1}; std::vector const int_base_2_test{"0", "1", "100", "010", "-1"}; for (std::size_t i = 0; i < int_base_2_test.size(); ++i) { auto const f = int_base_2_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 2); if (answer.ec != std::errc()) { std::cerr << "could not convert to int for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != int_base_2_test_expected[i]) { std::cerr << "result " << f << " did not match with expected int: " << int_base_2_test_expected[i] << std::endl; return EXIT_FAILURE; } } // unsigned base 2 test std::vector const unsigned_base_2_test_expected{0, 1, 4, 2}; std::vector const unsigned_base_2_test{"0", "1", "100", "010"}; for (std::size_t i = 0; i < unsigned_base_2_test.size(); ++i) { auto const &f = unsigned_base_2_test[i]; unsigned result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 2); if (answer.ec != std::errc()) { std::cerr << "could not convert to unsigned for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != unsigned_base_2_test_expected[i]) { std::cerr << "result " << f << " did not match with expected unsigned: " << unsigned_base_2_test_expected[i] << std::endl; return EXIT_FAILURE; } } // int invalid error base 2 test std::vector const int_invalid_argument_base_2_test{"2", "A", "-2"}; for (std::size_t i = 0; i < int_invalid_argument_base_2_test.size(); ++i) { auto const &f = int_invalid_argument_base_2_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 2); if (answer.ec != std::errc::invalid_argument) { std::cerr << "expected error should be 'invalid_argument' for: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } } // unsigned invalid error base 2 test std::vector const unsigned_invalid_argument_base_2_test{ "2", "A", "-1", "-2"}; for (std::size_t i = 0; i < unsigned_invalid_argument_base_2_test.size(); ++i) { auto const &f = unsigned_invalid_argument_base_2_test[i]; unsigned result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 2); if (answer.ec != std::errc::invalid_argument) { std::cerr << "expected error should be 'invalid_argument' for: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } } // octal test std::vector const base_octal_test_expected{0, 1, 7, 8, 9}; std::vector const base_octal_test{"0", "1", "07", "010", "0011"}; for (std::size_t i = 0; i < base_octal_test.size(); ++i) { auto const &f = base_octal_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 8); if (answer.ec != std::errc()) { std::cerr << "could not convert to int for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != base_octal_test_expected[i]) { std::cerr << "result " << f << " did not match with expected int: " << base_octal_test_expected[i] << std::endl; return EXIT_FAILURE; } } // hex test std::vector const base_hex_test_expected{0, 1, 15, 31, 0, 16}; std::vector const base_hex_test{"0", "1", "F", "01f", "0x11", "10X11"}; for (std::size_t i = 0; i < base_hex_test.size(); ++i) { auto const &f = base_hex_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 16); if (answer.ec != std::errc()) { std::cerr << "could not convert to int for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != base_hex_test_expected[i]) { std::cerr << "result " << f << " did not match with expected int: " << base_hex_test_expected[i] << std::endl; return EXIT_FAILURE; } } // invalid base test #1 (-1) std::vector const invalid_base_test_1{"0", "1", "-1", "F", "10Z"}; for (std::size_t i = 0; i < invalid_base_test_1.size(); ++i) { auto const &f = invalid_base_test_1[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, -1); if (answer.ec != std::errc::invalid_argument) { std::cerr << "expected error should be 'invalid_argument' for: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } } // invalid base test #2 (37) std::vector const invalid_base_test_2{"0", "1", "F", "Z", "10Z"}; for (std::size_t i = 0; i < invalid_base_test_2.size(); ++i) { auto const &f = invalid_base_test_2[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, 37); if (answer.ec != std::errc::invalid_argument) { std::cerr << "expected error should be 'invalid_argument' for: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } } // int out of range error base test (64 bit) std::vector const int_out_of_range_base_test{ "1000000000000000000000000000000000000000000000000000000000000000", "-1000000000000000000000000000000000000000000000000000000000000001", "2021110011022210012102010021220101220222", "-2021110011022210012102010021220101221000", "20000000000000000000000000000000", "-20000000000000000000000000000001", "1104332401304422434310311213", "-1104332401304422434310311214", "1540241003031030222122212", "-1540241003031030222122213", "22341010611245052052301", "-22341010611245052052302", "1000000000000000000000", "-1000000000000000000001", "67404283172107811828", "-67404283172107811830", "9223372036854775808", "-9223372036854775809", "1728002635214590698", "-1728002635214590699", "41A792678515120368", "-41A792678515120369", "10B269549075433C38", "-10B269549075433C39", "4340724C6C71DC7A8", "-4340724C6C71DC7A9", "160E2AD3246366808", "-160E2AD3246366809", "8000000000000000", "-8000000000000001", "33D3D8307B214009", "-33D3D8307B21400A", "16AGH595DF825FA8", "-16AGH595DF825FA9", "BA643DCI0FFEEHI", "-BA643DCI0FFEEI0", "5CBFJIA3FH26JA8", "-5CBFJIA3FH26JA9", "2HEICIIIE82DH98", "-2HEICIIIE82DH99", "1ADAIBB21DCKFA8", "-1ADAIBB21DCKFA9", "I6K448CF4192C3", "-I6K448CF4192C4", "ACD772JNC9L0L8", "-ACD772JNC9L0L9", "64IE1FOCNN5G78", "-64IE1FOCNN5G79", "3IGOECJBMCA688", "-3IGOECJBMCA689", "27C48L5B37OAOQ", "-27C48L5B37OAP0", "1BK39F3AH3DMQ8", "-1BK39F3AH3DMQ9", "Q1SE8F0M04ISC", "-Q1SE8F0M04ISD", "HAJPPBC1FC208", "-HAJPPBC1FC209", "BM03I95HIA438", "-BM03I95HIA439", "8000000000000", "-8000000000001", "5HG4CK9JD4U38", "-5HG4CK9JD4U39", "3TDTK1V8J6TPQ", "-3TDTK1V8J6TPR", "2PIJMIKEXRXP8", "-2PIJMIKEXRXP9", "1Y2P0IJ32E8E8", "-1Y2P0IJ32E8E9"}; for (std::size_t i = 0; i < int_out_of_range_base_test.size(); ++i) { auto const &f = int_out_of_range_base_test[i]; int64_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, int(2 + (i / 2))); 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; } } // unsigned out of range error base test (64 bit) std::vector const unsigned_out_of_range_base_test{ "10000000000000000000000000000000000000000000000000000000000000000", "11112220022122120101211020120210210211221", "100000000000000000000000000000000", "2214220303114400424121122431", "3520522010102100444244424", "45012021522523134134602", "2000000000000000000000", "145808576354216723757", "18446744073709551616", "335500516A429071285", "839365134A2A240714", "219505A9511A867B73", "8681049ADB03DB172", "2C1D56B648C6CD111", "10000000000000000", "67979G60F5428011", "2D3FGB0B9CG4BD2G", "141C8786H1CCAAGH", "B53BJH07BE4DJ0G", "5E8G4GGG7G56DIG", "2L4LF104353J8KG", "1DDH88H2782I516", "L12EE5FN0JI1IG", "C9C336O0MLB7EG", "7B7N2PCNIOKCGG", "4EO8HFAM6FLLMP", "2NC6J26L66RHOG", "1N3RSH11F098RO", "14L9LKMO30O40G", "ND075IB45K86G", "G000000000000", "B1W8P7J5Q9R6G", "7ORP63SH4DPHI", "5G24A25TWKWFG", "3W5E11264SGSG"}; int base_unsigned = 2; for (std::size_t i = 0; i < unsigned_out_of_range_base_test.size(); ++i) { auto const &f = unsigned_out_of_range_base_test[i]; uint64_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, base_unsigned); 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; } ++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 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 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 const int_within_range_base_test{ "111111111111111111111111111111111111111111111111111111111111111", "-1000000000000000000000000000000000000000000000000000000000000000", "2021110011022210012102010021220101220221", "-2021110011022210012102010021220101220222", "13333333333333333333333333333333", "-20000000000000000000000000000000", "1104332401304422434310311212", "-1104332401304422434310311213", "1540241003031030222122211", "-1540241003031030222122212", "22341010611245052052300", "-22341010611245052052301", "777777777777777777777", "-1000000000000000000000", "67404283172107811827", "-67404283172107811828", "9223372036854775807", "-9223372036854775808", "1728002635214590697", "-1728002635214590698", "41A792678515120367", "-41A792678515120368", "10B269549075433C37", "-10B269549075433C38", "4340724C6C71DC7A7", "-4340724C6C71DC7A8", "160E2AD3246366807", "-160E2AD3246366808", "7FFFFFFFFFFFFFFF", "-8000000000000000", "33D3D8307B214008", "-33D3D8307B214009", "16AGH595DF825FA7", "-16AGH595DF825FA8", "BA643DCI0FFEEHH", "-BA643DCI0FFEEHI", "5CBFJIA3FH26JA7", "-5CBFJIA3FH26JA8", "2HEICIIIE82DH97", "-2HEICIIIE82DH98", "1ADAIBB21DCKFA7", "-1ADAIBB21DCKFA8", "I6K448CF4192C2", "-I6K448CF4192C3", "ACD772JNC9L0L7", "-ACD772JNC9L0L8", "64IE1FOCNN5G77", "-64IE1FOCNN5G78", "3IGOECJBMCA687", "-3IGOECJBMCA688", "27C48L5B37OAOP", "-27C48L5B37OAOQ", "1BK39F3AH3DMQ7", "-1BK39F3AH3DMQ8", "Q1SE8F0M04ISB", "-Q1SE8F0M04ISC", "HAJPPBC1FC207", "-HAJPPBC1FC208", "BM03I95HIA437", "-BM03I95HIA438", "7VVVVVVVVVVVV", "-8000000000000", "5HG4CK9JD4U37", "-5HG4CK9JD4U38", "3TDTK1V8J6TPP", "-3TDTK1V8J6TPQ", "2PIJMIKEXRXP7", "-2PIJMIKEXRXP8", "1Y2P0IJ32E8E7", "-1Y2P0IJ32E8E8"}; for (std::size_t i = 0; i < int_within_range_base_test.size(); ++i) { auto const &f = int_within_range_base_test[i]; int64_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, int(2 + (i / 2))); if (answer.ec != std::errc()) { std::cerr << "converting " << f << " to int failed (most likely out of range)" << std::endl; return EXIT_FAILURE; } } // unsigned within range base test (64 bit) std::vector const unsigned_within_range_base_test{ "1111111111111111111111111111111111111111111111111111111111111111", "11112220022122120101211020120210210211220", "33333333333333333333333333333333", "2214220303114400424121122430", "3520522010102100444244423", "45012021522523134134601", "1777777777777777777777", "145808576354216723756", "18446744073709551615", "335500516A429071284", "839365134A2A240713", "219505A9511A867B72", "8681049ADB03DB171", "2C1D56B648C6CD110", "FFFFFFFFFFFFFFFF", "67979G60F5428010", "2D3FGB0B9CG4BD2F", "141C8786H1CCAAGG", "B53BJH07BE4DJ0F", "5E8G4GGG7G56DIF", "2L4LF104353J8KF", "1DDH88H2782I515", "L12EE5FN0JI1IF", "C9C336O0MLB7EF", "7B7N2PCNIOKCGF", "4EO8HFAM6FLLMO", "2NC6J26L66RHOF", "1N3RSH11F098RN", "14L9LKMO30O40F", "ND075IB45K86F", "FVVVVVVVVVVVV", "B1W8P7J5Q9R6F", "7ORP63SH4DPHH", "5G24A25TWKWFF", "3W5E11264SGSF"}; int base_unsigned2 = 2; for (std::size_t i = 0; i < unsigned_within_range_base_test.size(); ++i) { auto const &f = unsigned_within_range_base_test[i]; uint64_t result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, base_unsigned2); if (answer.ec != std::errc()) { std::cerr << "converting " << f << " to unsigned failed (most likely out of range)" << std::endl; return EXIT_FAILURE; } ++base_unsigned2; } // int leading zeros test std::vector const int_leading_zeros_test{ "000000000000000000000000000000000000000000000000000000000000000000000011" "11110111", "000000000000000000000000000000000000000000000000001101121", "000000000000000000000000000000000000000033313", "00000000000000000000000000000013030", "0000000000000000000000000000004411", "0000000000000000000000000000002650", "0000000000000000000000000000001767", "0000000000000000000000000000001347", "0000000000000000000000000000001015", "00000000000000000000843", "00000000000000000000707", "00000000000000000000601", "00000000000000000000527", "0000000000000000000047A", "000000000000000000003F7", "0000000000000000000038C", "00000000000000000000327", "000000000000000000002F8", "000000000000000000002AF", "00000000000000000000267", "00000000000000000000223", "000000000000000000001L3", "000000000000000000001I7", "000000000000000000001FF", "000000000000000000001D1", "000000000000000000001AG", "00000000000000000000187", "00000000000000000000160", "0000000000000000000013P", "0000000000000000000011N", "00000000000000000000VN", "00000000000000000000UP", "00000000000000000000TT", "00000000000000000000T0", "00000000000000000000S7"}; for (std::size_t i = 0; i < int_leading_zeros_test.size(); ++i) { auto const &f = int_leading_zeros_test[i]; int result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, int(i + 2)); if (answer.ec != std::errc()) { std::cerr << "could not convert to int for input: \"" << f << "\"" << std::endl; return EXIT_FAILURE; } else if (result != 1015) { std::cerr << "result " << f << " did not match with expected int: " << 1015 << std::endl; return EXIT_FAILURE; } } // issue 235 { std::vector s = {'0'}; s.shrink_to_fit(); int foo; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), foo); if (answer.ec != std::errc()) { std::cerr << "could not convert to int for input: '0'" << std::endl; return EXIT_FAILURE; } else if (foo != 0) { std::cerr << "expected zero: " << foo << std::endl; return EXIT_FAILURE; } } // dont parse UTF-16 code units of emojis as int if low byte is ascii digit { const std::u16string emojis[] = { u"ℹ", u"ℹ️", u"☸", u"☸️", u"☹", u"☹️", u"✳", u"✳️", u"✴", u"✴️", u"⤴", u"⤴️", u"⤵", u"⤵️", u"〰", u"〰️", }; bool failed = false; auto array_size = sizeof(emojis) / sizeof(emojis[0]); for (size_t i = 0; i < array_size; i++) { auto e = emojis[i]; int foo; auto answer = fast_float::from_chars(e.data(), e.data() + e.size(), foo); if (answer.ec == std::errc()) { failed = true; std::cerr << "Incorrectly parsed emoji #" << i << " as integer " << foo << "." << std::endl; } } if (failed) { return EXIT_FAILURE; } } // dont parse UTF-32 code points of emojis as int if low byte is ascii digit { const std::u32string emojis[] = { U"ℹ", U"ℹ️", U"☸", U"☸️", U"☹", U"☹️", U"✳", U"✳️", U"✴", U"✴️", U"⤴", U"⤴️", U"⤵", U"⤵️", U"〰", U"〰️", U"🈲", U"🈳", U"🈴", U"🈵", U"🈶", U"🈷", U"🈷️", U"🈸", U"🈹", U"🌰", U"🌱", U"🌲", U"🌳", U"🌴", U"🌵", U"🌶", U"🌶️", U"🌷", U"🌸", U"🌹", U"🐰", U"🐱", U"🐲", U"🐳", U"🐴", U"🐵", U"🐶", U"🐷", U"🐸", U"🐹", U"🔰", U"🔱", U"🔲", U"🔳", U"🔴", U"🔵", U"🔶", U"🔷", U"🔸", U"🔹", U"😰", U"😱", U"😲", U"😳", U"😴", U"😵", U"😵‍💫", U"😶", U"😶‍🌫", U"😶‍🌫️", U"😷", U"😸", U"😹", U"🤰", U"🤰🏻", U"🤰🏼", U"🤰🏽", U"🤰🏾", U"🤰🏿", U"🤱", U"🤱🏻", U"🤱🏼", U"🤱🏽", U"🤱🏾", U"🤱🏿", U"🤲", U"🤲🏻", U"🤲🏼", U"🤲🏽", U"🤲🏾", U"🤲🏿", U"🤳", U"🤳🏻", U"🤳🏼", U"🤳🏽", U"🤳🏾", U"🤳🏿", U"🤴", U"🤴🏻", U"🤴🏼", U"🤴🏽", U"🤴🏾", U"🤴🏿", U"🤵", U"🤵‍♀", U"🤵‍♀️", U"🤵‍♂", U"🤵‍♂️", U"🤵🏻", U"🤵🏻‍♀", U"🤵🏻‍♀️", U"🤵🏻‍♂", U"🤵🏻‍♂️", U"🤵🏼", U"🤵🏼‍♀", U"🤵🏼‍♀️", U"🤵🏼‍♂", U"🤵🏼‍♂️", U"🤵🏽", U"🤵🏽‍♀", U"🤵🏽‍♀️", U"🤵🏽‍♂", U"🤵🏽‍♂️", U"🤵🏾", U"🤵🏾‍♀", U"🤵🏾‍♀️", U"🤵🏾‍♂", U"🤵🏾‍♂️", U"🤵🏿", U"🤵🏿‍♀", U"🤵🏿‍♀️", U"🤵🏿‍♂", U"🤵🏿‍♂️", U"🤶", U"🤶🏻", U"🤶🏼", U"🤶🏽", U"🤶🏾", U"🤶🏿", U"🤷", U"🤷‍♀", U"🤷‍♀️", U"🤷‍♂", U"🤷‍♂️", U"🤷🏻", U"🤷🏻‍♀", U"🤷🏻‍♀️", U"🤷🏻‍♂", U"🤷🏻‍♂️", U"🤷🏼", U"🤷🏼‍♀", U"🤷🏼‍♀️", U"🤷🏼‍♂", U"🤷🏼‍♂️", U"🤷🏽", U"🤷🏽‍♀", U"🤷🏽‍♀️", U"🤷🏽‍♂", U"🤷🏽‍♂️", U"🤷🏾", U"🤷🏾‍♀", U"🤷🏾‍♀️", U"🤷🏾‍♂", U"🤷🏾‍♂️", U"🤷🏿", U"🤷🏿‍♀", U"🤷🏿‍♀️", U"🤷🏿‍♂", U"🤷🏿‍♂️", U"🤸", U"🤸‍♀", U"🤸‍♀️", U"🤸‍♂", U"🤸‍♂️", U"🤸🏻", U"🤸🏻‍♀", U"🤸🏻‍♀️", U"🤸🏻‍♂", U"🤸🏻‍♂️", U"🤸🏼", U"🤸🏼‍♀", U"🤸🏼‍♀️", U"🤸🏼‍♂", U"🤸🏼‍♂️", U"🤸🏽", U"🤸🏽‍♀", U"🤸🏽‍♀️", U"🤸🏽‍♂", U"🤸🏽‍♂️", U"🤸🏾", U"🤸🏾‍♀", U"🤸🏾‍♀️", U"🤸🏾‍♂", U"🤸🏾‍♂️", U"🤸🏿", U"🤸🏿‍♀", U"🤸🏿‍♀️", U"🤸🏿‍♂", U"🤸🏿‍♂️", U"🤹", U"🤹‍♀", U"🤹‍♀️", U"🤹‍♂", U"🤹‍♂️", U"🤹🏻", U"🤹🏻‍♀", U"🤹🏻‍♀️", U"🤹🏻‍♂", U"🤹🏻‍♂️", U"🤹🏼", U"🤹🏼‍♀", U"🤹🏼‍♀️", U"🤹🏼‍♂", U"🤹🏼‍♂️", U"🤹🏽", U"🤹🏽‍♀", U"🤹🏽‍♀️", U"🤹🏽‍♂", U"🤹🏽‍♂️", U"🤹🏾", U"🤹🏾‍♀", U"🤹🏾‍♀️", U"🤹🏾‍♂", U"🤹🏾‍♂️", U"🤹🏿", U"🤹🏿‍♀", U"🤹🏿‍♀️", U"🤹🏿‍♂", U"🤹🏿‍♂️", }; bool failed = false; auto array_size = sizeof(emojis) / sizeof(emojis[0]); for (size_t i = 0; i < array_size; i++) { auto e = emojis[i]; int foo; auto answer = fast_float::from_chars(e.data(), e.data() + e.size(), foo); if (answer.ec == std::errc()) { failed = true; std::cerr << "Incorrectly parsed emoji #" << i << " as integer " << foo << "." << std::endl; } } if (failed) { return EXIT_FAILURE; } } // The uint8_t and uint16_t base-10 paths use a byte-oriented fast path. A // wider code unit whose low byte is an ASCII digit (e.g. U+2131..U+2139) must // not be mistaken for that digit. The generic path already rejects these for // int; the fixed-width fast paths must agree. Lengths of 1..5 exercise both // the uint8_t path and the 4-digit uint16_t SWAR path. { const std::u16string bad16[] = { u"ℹ", u"ℱℲ", u"ℱℲℳ", u"ℱℲℳℴ", u"ℱℲℳℴℵ", }; const std::u32string bad32[] = { U"ℹ", U"ℱℲℳ", U"ℱℲℳℴ", U"ℱℲℳℴℵ", }; bool failed = false; for (auto const &s : bad16) { uint8_t r8 = 123; auto a8 = fast_float::from_chars(s.data(), s.data() + s.size(), r8); if (a8.ec == std::errc()) { failed = true; std::cerr << "Incorrectly parsed wide units as uint8_t " << unsigned(r8) << "." << std::endl; } uint16_t r16 = 123; auto a16 = fast_float::from_chars(s.data(), s.data() + s.size(), r16); if (a16.ec == std::errc()) { failed = true; std::cerr << "Incorrectly parsed wide units as uint16_t " << r16 << "." << std::endl; } } for (auto const &s : bad32) { uint8_t r8 = 123; auto a8 = fast_float::from_chars(s.data(), s.data() + s.size(), r8); if (a8.ec == std::errc()) { failed = true; std::cerr << "Incorrectly parsed wide units as uint8_t " << unsigned(r8) << "." << std::endl; } uint16_t r16 = 123; auto a16 = fast_float::from_chars(s.data(), s.data() + s.size(), r16); if (a16.ec == std::errc()) { failed = true; std::cerr << "Incorrectly parsed wide units as uint16_t " << r16 << "." << std::endl; } } if (failed) { return EXIT_FAILURE; } } // Comprehensive, oracle-checked u64 overflow detection across every base. // // The accumulator in parse_int_string is allowed to overflow and the result // is validated afterwards. At the max_digits boundary a value can wrap one or // more whole multiples of 2^64 (a 20-digit base-10 number reaches ~5.4*2^64), // so the boundary check must be exact. This section validates from_chars for // bases 2..36 against an independent, trusted oracle: a plain 64-bit checked // multiply-add. It hammers the single leading-digit band that straddles 2^64 // (where wrapped and non-wrapped values are hardest to tell apart) and also // covers max_digits-1 (always in range) and max_digits+1 (always overflow). { auto digit_to_char = [](int d) -> char { return d < 10 ? char('0' + d) : char('A' + (d - 10)); }; auto char_to_digit = [](char c) -> int { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'A' && c <= 'Z') { return c - 'A' + 10; } return c - 'a' + 10; }; // Trusted oracle: parse `s` in `base` with a checked 64-bit multiply-add. // Returns true on u64 overflow; otherwise writes the value to `out`. auto oracle = [&](std::string const &s, int base, uint64_t &out) -> bool { uint64_t v = 0; for (char c : s) { uint64_t const d = uint64_t(char_to_digit(c)); if (v > (UINT64_MAX - d) / uint64_t(base)) { return true; } v = uint64_t(base) * v + d; } out = v; return false; }; auto to_base = [&](uint64_t v, int base) -> std::string { if (v == 0) { return "0"; } std::string s; while (v != 0) { s += digit_to_char(int(v % uint64_t(base))); v /= uint64_t(base); } std::reverse(s.begin(), s.end()); return s; }; // Add one (in base `base`) to the digit string `s`, carrying as needed. auto increment = [&](std::string s, int base) -> std::string { int carry = 1; for (std::size_t k = s.size(); k-- > 0 && carry != 0;) { int const d = char_to_digit(s[k]) + carry; carry = d / base; s[k] = digit_to_char(d % base); } if (carry != 0) { s.insert(s.begin(), digit_to_char(carry)); } return s; }; // Subtract one (in base `base`) from a non-zero, non-negative string. auto decrement = [&](std::string s, int base) -> std::string { int borrow = 1; for (std::size_t k = s.size(); k-- > 0 && borrow != 0;) { int d = char_to_digit(s[k]) - borrow; borrow = d < 0 ? 1 : 0; if (d < 0) { d += base; } s[k] = digit_to_char(d); } std::size_t lead = s.find_first_not_of('0'); // drop any leading zero return lead == std::string::npos ? "0" : s.substr(lead); }; std::mt19937_64 rng(0xC0FFEEULL); long long checked = 0; auto verify = [&](std::string const &s, int base) -> bool { uint64_t expected = 0; bool const ov = oracle(s, base, expected); uint64_t result = 0xDEADBEEFULL; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, base); ++checked; if (ov) { if (answer.ec != std::errc::result_out_of_range) { std::cerr << "base " << base << ": expected result_out_of_range for \"" << s << "\"" << std::endl; return false; } } else { if (answer.ec != std::errc()) { std::cerr << "base " << base << ": unexpected error for \"" << s << "\"" << std::endl; return false; } if (result != expected) { std::cerr << "base " << base << ": \"" << s << "\" -> " << result << ", expected " << expected << std::endl; return false; } if (answer.ptr != s.data() + s.size()) { std::cerr << "base " << base << ": did not consume all of \"" << s << "\"" << std::endl; return false; } } return true; }; // Leading zeros are stripped before the digit count, so the outcome must be // unchanged. Checked only on hand-picked values (it exercises shared code). auto verify_zeros = [&](std::string const &digits, int base) -> bool { return verify(digits, base) && verify("0" + digits, base) && verify(std::string(40, '0') + digits, base); }; auto random_tail = [&](std::string &s, int n, int base) { for (int k = 0; k < n; ++k) { // bias toward the extremes (0 and base-1) to hit boundaries often std::uint64_t const r = rng(); int const mode = int(r % 4); int const dig = mode == 0 ? 0 : mode == 1 ? base - 1 : int((r >> 2) % std::uint64_t(base)); s += digit_to_char(dig); } }; for (int base = 2; base <= 36; ++base) { // M = max number of base-`base` digits a u64 can hold. std::string const maxstr = to_base(UINT64_MAX, base); int const M = int(maxstr.size()); // b^(M-1): smallest M-digit value, and width of each leading-digit band. uint64_t bM1 = 1; for (int k = 0; k < M - 1; ++k) { bM1 *= uint64_t(base); } int const dmax = int(UINT64_MAX / bM1); // largest leading digit that fits // Exact-boundary sweep straddling 2^64 (the hardest transition): the // 64 values UINT64_MAX-31 .. UINT64_MAX (in range) and 2^64 .. 2^64+31 // (overflow), built by walking the digit string up and down. std::string below = maxstr, above = increment(maxstr, base); for (int k = 0; k < 32; ++k) { if (!verify(below, base) || !verify(above, base)) { return EXIT_FAILURE; } below = decrement(below, base); above = increment(above, base); } // Hand-picked values, also checked with leading zeros. std::string const allmax(std::size_t(M), digit_to_char(base - 1)); if (!verify_zeros(maxstr, base) || // largest in-range value !verify_zeros(increment(maxstr, base), base) || // smallest overflow !verify_zeros(allmax, base)) { // largest M-digit (multi-wrap) return EXIT_FAILURE; } // Randomized M-digit values across every leading digit. Bands with // lead > dmax always overflow (this is where the naive min_safe check // wrongly accepted multi-wrap values); lead < dmax always fits; lead == // dmax straddles 2^64 and gets the heaviest sampling. for (int lead = 1; lead < base; ++lead) { int const trials = lead == dmax ? 4000 : 300; for (int trial = 0; trial < trials; ++trial) { std::string s(1, digit_to_char(lead)); random_tail(s, M - 1, base); if (!verify(s, base)) { return EXIT_FAILURE; } } } // max_digits-1 digits never overflow; max_digits+1 digits always do. for (int trial = 0; trial < 500; ++trial) { std::string shorts(1, digit_to_char(1 + int(rng() % uint64_t(base - 1)))); random_tail(shorts, M - 2, base); std::string longs(1, digit_to_char(1 + int(rng() % uint64_t(base - 1)))); random_tail(longs, M, base); if (!verify(shorts, base) || !verify(longs, base)) { return EXIT_FAILURE; } } } if (checked < 100000) { std::cerr << "overflow sweep ran too few cases: " << checked << std::endl; return EXIT_FAILURE; } } // Signed (int64_t) boundary: every value that overflows u64 also overflows // i64, and the exact i64 limits must parse. Reuses the oracle indirectly via // hand-built extremes per base. { auto digit_to_char = [](int d) -> char { return d < 10 ? char('0' + d) : char('A' + (d - 10)); }; auto to_base_signed = [&](int64_t value, int base) -> std::string { // value may be INT64_MIN; accumulate magnitude in u64 to avoid UB. bool const neg = value < 0; uint64_t mag = neg ? (~uint64_t(value) + 1) : uint64_t(value); std::string s; if (mag == 0) { s = "0"; } while (mag != 0) { s += digit_to_char(int(mag % uint64_t(base))); mag /= uint64_t(base); } if (neg) { s += '-'; } std::reverse(s.begin(), s.end()); return s; }; for (int base = 2; base <= 36; ++base) { struct { int64_t v; } const limits[] = {{INT64_MAX}, {INT64_MIN}, {0}, {-1}, {1}}; for (auto const &lim : limits) { std::string const s = to_base_signed(lim.v, base); int64_t result = 123; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, base); if (answer.ec != std::errc() || result != lim.v) { std::cerr << "base " << base << ": signed limit \"" << s << "\" failed to round-trip (got " << result << ")" << std::endl; return EXIT_FAILURE; } } // Increment a non-negative magnitude string (in `base`) by one. auto inc_mag = [&](std::string m) -> std::string { int carry = 1; for (std::size_t k = m.size(); k-- > 0 && carry != 0;) { int d = (m[k] >= '0' && m[k] <= '9') ? m[k] - '0' : (m[k] >= 'A' && m[k] <= 'Z') ? m[k] - 'A' + 10 : m[k] - 'a' + 10; d += carry; carry = d / base; m[k] = digit_to_char(d % base); } if (carry != 0) { m.insert(m.begin(), digit_to_char(carry)); } return m; }; // INT64_MAX + 1 (= 2^63) overflows a positive int64_t. // INT64_MIN - 1 (= -(2^63 + 1)) overflows a negative int64_t. // Note that -(2^63) == INT64_MIN is in range and is covered above. std::string const max_mag = to_base_signed(INT64_MAX, base); // 2^63 - 1 std::string const over = inc_mag(max_mag); // 2^63 std::string const under = "-" + inc_mag(over); // -(2^63 + 1) for (std::string const &s : {over, under}) { int64_t result = 123; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, base); if (answer.ec != std::errc::result_out_of_range) { std::cerr << "base " << base << ": expected result_out_of_range for " << "signed \"" << s << "\"" << std::endl; return EXIT_FAILURE; } } } } return EXIT_SUCCESS; } #else #include #include int main() { std::cerr << "The test requires C++17." << std::endl; return EXIT_SUCCESS; } #endif