| #include "fast_float/fast_float.h" | |
| #include <cstdint> | |
| #include <ios> | |
| #include <iostream> | |
| #include <random> | |
| #include <system_error> | |
| #include <utility> | |
| #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ | |
| defined(sun) || defined(__sun) | |
| // Anything at all that is related to cygwin, msys and so forth will | |
| // always use this fallback because we cannot rely on it behaving as normal | |
| // gcc. | |
| #include <locale> | |
| #include <sstream> | |
| // workaround for CYGWIN | |
| double cygwin_strtod_l(char const *start, char **end) { | |
| double d; | |
| std::stringstream ss; | |
| ss.imbue(std::locale::classic()); | |
| ss << start; | |
| ss >> d; | |
| if (ss.fail()) { | |
| *end = nullptr; | |
| } | |
| if (ss.eof()) { | |
| ss.clear(); | |
| } | |
| auto nread = ss.tellg(); | |
| *end = const_cast<char *>(start) + nread; | |
| return d; | |
| } | |
| float cygwin_strtof_l(char const *start, char **end) { | |
| float d; | |
| std::stringstream ss; | |
| ss.imbue(std::locale::classic()); | |
| ss << start; | |
| ss >> d; | |
| if (ss.fail()) { | |
| *end = nullptr; | |
| } | |
| if (ss.eof()) { | |
| ss.clear(); | |
| } | |
| auto nread = ss.tellg(); | |
| *end = const_cast<char *>(start) + nread; | |
| return d; | |
| } | |
| #endif | |
| class RandomEngine { | |
| public: | |
| RandomEngine() = delete; | |
| RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed){}; | |
| uint64_t next() { | |
| // Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h | |
| // Inspired from | |
| // https://github.com/lemire/testingRNG/blob/master/source/wyhash.h | |
| wyhash64_x_ += UINT64_C(0x60bee2bee120fc15); | |
| fast_float::value128 tmp = fast_float::full_multiplication( | |
| wyhash64_x_, UINT64_C(0xa3b195354a39b70d)); | |
| uint64_t m1 = (tmp.high) ^ tmp.low; | |
| tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9)); | |
| uint64_t m2 = (tmp.high) ^ tmp.low; | |
| return m2; | |
| } | |
| bool next_bool() { return (next() & 1) == 1; } | |
| int next_int() { return static_cast<int>(next()); } | |
| char next_char() { return static_cast<char>(next()); } | |
| double next_double() { return static_cast<double>(next()); } | |
| int next_ranged_int(int min, int max) { // min and max are included | |
| // Adapted from | |
| // https://lemire.me/blog/2019/06/06/nearly-divisionless-random-integer-generation-on-various-systems/ | |
| /* if (min == max) { | |
| return min; | |
| }*/ | |
| uint64_t s = uint64_t(max - min + 1); | |
| uint64_t x = next(); | |
| fast_float::value128 m = fast_float::full_multiplication(x, s); | |
| uint64_t l = m.low; | |
| if (l < s) { | |
| uint64_t t = -s % s; | |
| while (l < t) { | |
| x = next(); | |
| m = fast_float::full_multiplication(x, s); | |
| l = m.low; | |
| } | |
| } | |
| return int(m.high) + min; | |
| } | |
| int next_digit() { return next_ranged_int(0, 9); } | |
| private: | |
| uint64_t wyhash64_x_; | |
| }; | |
| size_t build_random_string(RandomEngine &rand, char *buffer) { | |
| size_t pos{0}; | |
| if (rand.next_bool()) { | |
| buffer[pos++] = '-'; | |
| } | |
| int number_of_digits = rand.next_ranged_int(1, 100); | |
| if (number_of_digits == 100) { | |
| // With low probability, we want to allow very long strings just to stress | |
| // the system. | |
| number_of_digits = rand.next_ranged_int(1, 2000); | |
| } | |
| int location_of_decimal_separator = rand.next_ranged_int(1, number_of_digits); | |
| for (size_t i = 0; i < size_t(number_of_digits); i++) { | |
| if (i == size_t(location_of_decimal_separator)) { | |
| buffer[pos++] = '.'; | |
| } | |
| buffer[pos] = char(rand.next_digit() + '0'); | |
| // We can have a leading zero only if location_of_decimal_separator = 1. | |
| while (i == 0 && 1 != size_t(location_of_decimal_separator) && | |
| buffer[pos] == '0') { | |
| buffer[pos] = char(rand.next_digit() + '0'); | |
| } | |
| pos++; | |
| } | |
| if (rand.next_bool()) { | |
| if (rand.next_bool()) { | |
| buffer[pos++] = 'e'; | |
| } else { | |
| buffer[pos++] = 'E'; | |
| } | |
| if (rand.next_bool()) { | |
| buffer[pos++] = '-'; | |
| } else { | |
| if (rand.next_bool()) { | |
| buffer[pos++] = '+'; | |
| } | |
| } | |
| number_of_digits = rand.next_ranged_int(1, 3); | |
| for (size_t i = 0; i < size_t(number_of_digits); i++) { | |
| buffer[pos++] = char(rand.next_digit() + '0'); | |
| } | |
| } | |
| buffer[pos] = '\0'; // null termination | |
| return pos; | |
| } | |
| std::pair<double, bool> strtod_from_string(char *st) { | |
| double d; | |
| char *pr; | |
| #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ | |
| defined(sun) || defined(__sun) | |
| d = cygwin_strtod_l(st, &pr); | |
| #elif defined(_WIN32) | |
| static _locale_t c_locale = _create_locale(LC_ALL, "C"); | |
| d = _strtod_l(st, &pr, c_locale); | |
| #else | |
| static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); | |
| d = strtod_l(st, &pr, c_locale); | |
| #endif | |
| if (st == pr) { | |
| std::cerr << "strtod_l could not parse '" << st << std::endl; | |
| return std::make_pair(0, false); | |
| } | |
| return std::make_pair(d, true); | |
| } | |
| std::pair<float, bool> strtof_from_string(char *st) { | |
| float d; | |
| char *pr; | |
| #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ | |
| defined(sun) || defined(__sun) | |
| d = cygwin_strtof_l(st, &pr); | |
| #elif defined(_WIN32) | |
| static _locale_t c_locale = _create_locale(LC_ALL, "C"); | |
| d = _strtof_l(st, &pr, c_locale); | |
| #else | |
| static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); | |
| d = strtof_l(st, &pr, c_locale); | |
| #endif | |
| if (st == pr) { | |
| std::cerr << "strtof_l could not parse '" << st << std::endl; | |
| return std::make_pair(0.0f, false); | |
| } | |
| return std::make_pair(d, true); | |
| } | |
| /** | |
| * We generate random strings and we try to parse them with both strtod/strtof, | |
| * and we verify that we get the same answer with with fast_float::from_chars. | |
| */ | |
| bool tester(uint64_t seed, size_t volume) { | |
| char buffer[4096]; // large buffer (can't overflow) | |
| RandomEngine rand(seed); | |
| for (size_t i = 0; i < volume; i++) { | |
| if ((i % 100000) == 0) { | |
| std::cout << "."; | |
| std::cout.flush(); | |
| } | |
| size_t length = build_random_string(rand, buffer); | |
| std::pair<double, bool> expected_double = strtod_from_string(buffer); | |
| if (expected_double.second) { | |
| double result_value; | |
| auto result = | |
| fast_float::from_chars(buffer, buffer + length, result_value); | |
| if (result.ec != std::errc() && | |
| result.ec != std::errc::result_out_of_range) { | |
| printf("parsing %.*s\n", int(length), buffer); | |
| std::cerr << " I could not parse " << std::endl; | |
| return false; | |
| } | |
| if (result.ptr != buffer + length) { | |
| printf("parsing %.*s\n", int(length), buffer); | |
| std::cerr << " Did not get to the end " << std::endl; | |
| return false; | |
| } | |
| if (result_value != expected_double.first) { | |
| printf("parsing %.*s\n", int(length), buffer); | |
| std::cerr << std::hexfloat << result_value << std::endl; | |
| std::cerr << std::hexfloat << expected_double.first << std::endl; | |
| std::cerr << " Mismatch " << std::endl; | |
| return false; | |
| } | |
| } | |
| std::pair<float, bool> expected_float = strtof_from_string(buffer); | |
| if (expected_float.second) { | |
| float result_value; | |
| auto result = | |
| fast_float::from_chars(buffer, buffer + length, result_value); | |
| if (result.ec != std::errc() && | |
| result.ec != std::errc::result_out_of_range) { | |
| printf("parsing %.*s\n", int(length), buffer); | |
| std::cerr << " I could not parse " << std::endl; | |
| return false; | |
| } | |
| if (result.ptr != buffer + length) { | |
| printf("parsing %.*s\n", int(length), buffer); | |
| std::cerr << " Did not get to the end " << std::endl; | |
| return false; | |
| } | |
| if (result_value != expected_float.first) { | |
| printf("parsing %.*s\n", int(length), buffer); | |
| std::cerr << std::hexfloat << result_value << std::endl; | |
| std::cerr << std::hexfloat << expected_float.first << std::endl; | |
| std::cerr << " Mismatch " << std::endl; | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| int main() { | |
| #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ | |
| defined(sun) || defined(__sun) | |
| std::cout << "Warning: msys/cygwin or solaris detected." << std::endl; | |
| return EXIT_SUCCESS; | |
| #else | |
| if (tester(1234344, 100000000)) { | |
| std::cout << "All tests ok." << std::endl; | |
| return EXIT_SUCCESS; | |
| } | |
| std::cout << "Failure." << std::endl; | |
| return EXIT_FAILURE; | |
| #endif | |
| } |