fast_float/tests/fast_int.cpp
2026-06-13 21:41:53 -04:00

1692 lines
59 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <cstdlib>
#include <iostream>
#include <vector>
#include <string_view>
#include <string>
#include <cstring>
#include <random>
#include <algorithm>
#include "fast_float/fast_float.h"
#include <cstdint>
/*
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<int> const int_basic_test_expected{0, 10, -40, 1001, 9};
std::vector<std::string_view> 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<unsigned> const unsigned_basic_test_expected{0, 10, 1001, 9};
std::vector<std::string_view> 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<char> const char_basic_test_expected{0, 10, 40, 100, 9};
std::vector<std::string_view> 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<int>(char_basic_test_expected[i]) << std::endl;
return EXIT_FAILURE;
}
}
// short basic test
std::vector<short> const short_basic_test_expected{0, 10, -40, 1001, 9};
std::vector<std::string_view> 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<long> const long_basic_test_expected{0, 10, -40, 1001, 9};
std::vector<std::string_view> 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<long long> const long_long_basic_test_expected{0, 10, -40, 1001,
9};
std::vector<std::string_view> 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<unsigned char> const unsigned_char_basic_test_expected{0, 10, 100,
9};
std::vector<std::string_view> 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<int>(unsigned_char_basic_test_expected[i])
<< std::endl;
return EXIT_FAILURE;
}
}
// unsigned short basic test
std::vector<unsigned short> const unsigned_short_basic_test_expected{0, 10,
1001, 9};
std::vector<std::string_view> 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<unsigned long> const unsigned_long_basic_test_expected{0, 10,
1001, 9};
std::vector<std::string_view> 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<unsigned long long> const unsigned_long_long_basic_test_expected{
0, 10, 1001, 9};
std::vector<std::string_view> 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<bool> const bool_basic_test_expected{false, true};
std::vector<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<int> const int_base_2_test_expected{0, 1, 4, 2, -1};
std::vector<std::string_view> 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<unsigned> const unsigned_base_2_test_expected{0, 1, 4, 2};
std::vector<std::string_view> 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<std::string_view> 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<std::string_view> 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<int> const base_octal_test_expected{0, 1, 7, 8, 9};
std::vector<std::string_view> 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<int> const base_hex_test_expected{0, 1, 15, 31, 0, 16};
std::vector<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<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",
"-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<std::string_view> 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<std::string_view> 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<char> 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 <iostream>
#include <cstdlib>
int main() {
std::cerr << "The test requires C++17." << std::endl;
return EXIT_SUCCESS;
}
#endif