diff --git a/tests/exhaustive32.cpp b/tests/exhaustive32.cpp index 5e7de2d..455d8af 100644 --- a/tests/exhaustive32.cpp +++ b/tests/exhaustive32.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include template char *to_string(T d, char *buffer) { auto written = std::snprintf(buffer, 64, "%.*e", @@ -15,47 +17,59 @@ template char *to_string(T d, char *buffer) { return buffer + written; } -void allvalues() { +// Checks a single 32-bit word (interpreted as a float); aborts on a mismatch. +void check_word(uint32_t word) { char buffer[64]; - for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { - float v; - if ((w % 1048576) == 0) { - std::cout << "."; - std::cout.flush(); - } - uint32_t word = uint32_t(w); - memcpy(&v, &word, sizeof(v)); + float v; + memcpy(&v, &word, sizeof(v)); - { - char const *string_end = to_string(v, buffer); - float result_value; - auto result = fast_float::from_chars(buffer, string_end, result_value); - // Starting with version 4.0 for fast_float, we return result_out_of_range - // if the value is either too small (too close to zero) or too large - // (effectively infinity). So std::errc::result_out_of_range is normal for - // well-formed input strings. - if (result.ec != std::errc() && - result.ec != std::errc::result_out_of_range) { - std::cerr << "parsing error ? " << buffer << std::endl; - abort(); - } - if (std::isnan(v)) { - if (!std::isnan(result_value)) { - std::cerr << "not nan" << buffer << std::endl; - abort(); - } - } else if (copysign(1, result_value) != copysign(1, v)) { - std::cerr << "I got " << std::hexfloat << result_value - << " but I was expecting " << v << std::endl; - abort(); - } else if (result_value != v) { - std::cerr << "no match ? " << buffer << std::endl; - std::cout << "started with " << std::hexfloat << v << std::endl; - std::cout << "got back " << std::hexfloat << result_value << std::endl; - std::cout << std::dec; - abort(); - } + char const *string_end = to_string(v, buffer); + float result_value; + auto result = fast_float::from_chars(buffer, string_end, result_value); + // Starting with version 4.0 for fast_float, we return result_out_of_range + // if the value is either too small (too close to zero) or too large + // (effectively infinity). So std::errc::result_out_of_range is normal for + // well-formed input strings. + if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) { + std::cerr << "parsing error ? " << buffer << std::endl; + abort(); + } + if (std::isnan(v)) { + if (!std::isnan(result_value)) { + std::cerr << "not nan" << buffer << std::endl; + abort(); } + } else if (copysign(1, result_value) != copysign(1, v)) { + std::cerr << "I got " << std::hexfloat << result_value + << " but I was expecting " << v << std::endl; + abort(); + } else if (result_value != v) { + std::cerr << "no match ? " << buffer << std::endl; + std::cout << "started with " << std::hexfloat << v << std::endl; + std::cout << "got back " << std::hexfloat << result_value << std::endl; + std::cout << std::dec; + abort(); + } +} + +// Sweeps the whole 2^32 float space, split across hardware threads (the values +// are independent); check_word() aborts on the first mismatch. +void allvalues() { + unsigned int nthreads = std::thread::hardware_concurrency(); + if (nthreads == 0) { + nthreads = 1; + } + std::vector workers; + workers.reserve(nthreads); + for (unsigned int t = 0; t < nthreads; t++) { + workers.emplace_back([t, nthreads]() { + for (uint64_t w = t; w <= 0xFFFFFFFF; w += nthreads) { + check_word(uint32_t(w)); + } + }); + } + for (std::thread &worker : workers) { + worker.join(); } std::cout << std::endl; } diff --git a/tests/exhaustive32_64.cpp b/tests/exhaustive32_64.cpp index c453f28..b520352 100644 --- a/tests/exhaustive32_64.cpp +++ b/tests/exhaustive32_64.cpp @@ -1,6 +1,7 @@ #include "fast_float/fast_float.h" +#include #include #include #include @@ -9,6 +10,8 @@ #include #include #include +#include +#include template char *to_string(T d, char *buffer) { auto written = std::snprintf(buffer, 64, "%.*e", @@ -45,25 +48,38 @@ bool basic_test_64bit(std::string vals, double val) { return true; } +// Sweeps the whole 2^32 float space (widened to double), split across hardware +// threads (the values are independent); stops at the first mismatch. void all_32bit_values() { - char buffer[64]; - for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { - float v32; - if ((w % 1048576) == 0) { - std::cout << "."; - std::cout.flush(); - } - uint32_t word = uint32_t(w); - memcpy(&v32, &word, sizeof(v32)); - double v = v32; + unsigned int nthreads = std::thread::hardware_concurrency(); + if (nthreads == 0) { + nthreads = 1; + } + std::atomic ok{true}; + std::vector workers; + workers.reserve(nthreads); + for (unsigned int t = 0; t < nthreads; t++) { + workers.emplace_back([t, nthreads, &ok]() { + char buffer[64]; + for (uint64_t w = t; + w <= 0xFFFFFFFF && ok.load(std::memory_order_relaxed); + w += nthreads) { + float v32; + uint32_t word = uint32_t(w); + memcpy(&v32, &word, sizeof(v32)); + double v = v32; - { - char const *string_end = to_string(v, buffer); - std::string s(buffer, size_t(string_end - buffer)); - if (!basic_test_64bit(s, v)) { - return; + char const *string_end = to_string(v, buffer); + std::string s(buffer, size_t(string_end - buffer)); + if (!basic_test_64bit(s, v)) { + ok.store(false, std::memory_order_relaxed); + return; + } } - } + }); + } + for (std::thread &worker : workers) { + worker.join(); } std::cout << std::endl; }