mirror of
https://github.com/fastfloat/fast_float.git
synced 2025-12-06 08:46:49 +08:00
274 lines
8.0 KiB
C++
274 lines
8.0 KiB
C++
#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
|
|
}
|