diff --git a/.github/workflows/vs17-cxx20.yml b/.github/workflows/vs17-cxx20.yml index 4231340..69476f9 100644 --- a/.github/workflows/vs17-cxx20.yml +++ b/.github/workflows/vs17-cxx20.yml @@ -18,8 +18,12 @@ jobs: - name: checkout uses: actions/checkout@v3 - name: configure - run: | - cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. + run: >- + cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} + -DCMAKE_CXX_STANDARD=20 + -DFASTFLOAT_TEST=ON + -DFASTFLOAT_CONSTEXPR_TESTS=ON + -DCMAKE_INSTALL_PREFIX:PATH=destination - name: build run: | cmake --build build --verbose --config ${{matrix.cfg}} --parallel diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 6f3e69d..72b8098 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -27,7 +27,16 @@ fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { | (val & 0x00000000000000FF) << 56; } -fastfloat_really_inline uint64_t read_u64(const char *chars) { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t read_u64(const char *chars) { + if (cpp20_and_in_constexpr()) { + uint64_t val = 0; + for(int i = 0; i < 8; ++i) { + val |= uint64_t(*chars) << (i*8); + ++chars; + } + return val; + } uint64_t val; ::memcpy(&val, chars, sizeof(uint64_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 @@ -37,7 +46,16 @@ fastfloat_really_inline uint64_t read_u64(const char *chars) { return val; } -fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void write_u64(uint8_t *chars, uint64_t val) { + if (cpp20_and_in_constexpr()) { + for(int i = 0; i < 8; ++i) { + *chars = uint8_t(val); + val >>= 8; + ++chars; + } + return; + } #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); @@ -57,7 +75,8 @@ uint32_t parse_eight_digits_unrolled(uint64_t val) { return uint32_t(val); } -fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { return parse_eight_digits_unrolled(read_u64(chars)); } @@ -67,7 +86,8 @@ fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val 0x8080808080808080)); } -fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool is_made_of_eight_digits_fast(const char *chars) noexcept { return is_made_of_eight_digits_fast(read_u64(chars)); } @@ -87,7 +107,7 @@ struct parsed_number_string { // Assuming that you use no more than 19 digits, this will // parse an ASCII string. -fastfloat_really_inline +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { const chars_format fmt = options.format; const char decimal_point = options.decimal_point; diff --git a/include/fast_float/bigint.h b/include/fast_float/bigint.h index 5a72caa..5076b47 100644 --- a/include/fast_float/bigint.h +++ b/include/fast_float/bigint.h @@ -514,7 +514,7 @@ struct bigint : pow5_tables<> { // move limbs limb* dst = vec.data + n; const limb* src = vec.data; - ::memmove(dst, src, sizeof(limb) * vec.len()); + std::copy_backward(src, src + vec.len(), dst + vec.len()); // fill in empty limbs limb* first = vec.data; limb* last = first + n; @@ -594,7 +594,12 @@ struct bigint : pow5_tables<> { exp -= small_step; } if (exp != 0) { - FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY( + small_mul(vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))) + ); } return true; diff --git a/include/fast_float/constexpr_feature_detect.h b/include/fast_float/constexpr_feature_detect.h new file mode 100644 index 0000000..9ec8611 --- /dev/null +++ b/include/fast_float/constexpr_feature_detect.h @@ -0,0 +1,40 @@ +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \ + && FASTFLOAT_HAS_BIT_CAST \ + && __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H diff --git a/include/fast_float/decimal_to_binary.h b/include/fast_float/decimal_to_binary.h index 64f3f5a..6d0f730 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -17,7 +17,7 @@ namespace fast_float { // low part corresponding to the least significant bits. // template -fastfloat_really_inline +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 compute_product_approximation(int64_t q, uint64_t w) { const int index = 2 * int(q - powers::smallest_power_of_five); // For small values of q, e.g., q in [0,27], the answer is always exact because @@ -76,7 +76,7 @@ adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept // w * 10 ** q, without rounding the representation up. // the power2 in the exponent will be adjusted by invalid_am_bias. template -fastfloat_really_inline +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { int lz = leading_zeroes(w); w <<= lz; @@ -90,7 +90,7 @@ adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { // return an adjusted_mantissa with a negative power of 2: the caller should recompute // in such cases. template -fastfloat_really_inline +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { adjusted_mantissa answer; if ((w == 0) || (q < binary::smallest_power_of_ten())) { diff --git a/include/fast_float/digit_comparison.h b/include/fast_float/digit_comparison.h index 4e0e6a8..3959ba0 100644 --- a/include/fast_float/digit_comparison.h +++ b/include/fast_float/digit_comparison.h @@ -44,7 +44,8 @@ int32_t scientific_exponent(parsed_number_string& num) noexcept { // this converts a native floating-point number to an extended-precision float. template -fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa to_extended(T value) noexcept { using equiv_uint = typename binary_format::equiv_uint; constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); @@ -53,7 +54,11 @@ fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { adjusted_mantissa am; int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else ::memcpy(&bits, &value, sizeof(T)); +#endif if ((bits & exponent_mask) == 0) { // denormal am.power2 = 1 - bias; @@ -72,7 +77,8 @@ fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { // we are given a native float that represents b, so we need to adjust it // halfway between b and b+u. template -fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa to_extended_halfway(T value) noexcept { adjusted_mantissa am = to_extended(value); am.mantissa <<= 1; am.mantissa += 1; @@ -148,9 +154,10 @@ void round_down(adjusted_mantissa& am, int32_t shift) noexcept { am.power2 += shift; } -fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void skip_zeros(const char*& first, const char* last) noexcept { uint64_t val; - while (std::distance(first, last) >= 8) { + while (!cpp20_and_in_constexpr() && std::distance(first, last) >= 8) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != 0x3030303030303030) { break; @@ -167,10 +174,11 @@ fastfloat_really_inline void skip_zeros(const char*& first, const char* last) no // determine if any non-zero digits were truncated. // all characters must be valid digits. -fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool is_truncated(const char* first, const char* last) noexcept { // do 8-bit optimizations, can just compare to 8 literal 0s. uint64_t val; - while (std::distance(first, last) >= 8) { + while (!cpp20_and_in_constexpr() && std::distance(first, last) >= 8) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != 0x3030303030303030) { return true; @@ -186,11 +194,12 @@ fastfloat_really_inline bool is_truncated(const char* first, const char* last) n return false; } -fastfloat_really_inline bool is_truncated(byte_span s) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool is_truncated(byte_span s) noexcept { return is_truncated(s.ptr, s.ptr + s.len()); } -fastfloat_really_inline +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { value = value * 100000000 + parse_eight_digits_unrolled(p); p += 8; @@ -206,13 +215,14 @@ void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count count++; } -fastfloat_really_inline +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void add_native(bigint& big, limb power, limb value) noexcept { big.mul(power); big.add(value); } -fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void round_up_bigint(bigint& big, size_t& count) noexcept { // need to round-up the digits, but need to avoid rounding // ....9999 to ...10000, which could cause a false halfway point. add_native(big, 10, 1); @@ -220,7 +230,8 @@ fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcep } // parse the significant digits into a big integer -inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { +inline FASTFLOAT_CONSTEXPR20 +void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { // try to minimize the number of big integer and scalar multiplication. // therefore, try to parse 8 digits at a time, and multiply by the largest // scalar value (9 or 19 digits) for each step. @@ -300,7 +311,8 @@ inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max } template -inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { +inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); adjusted_mantissa answer; bool truncated; @@ -323,7 +335,8 @@ inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) // we then need to scale by `2^(f- e)`, and then the two significant digits // are of the same magnitude. template -inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { +inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { bigint& real_digits = bigmant; int32_t real_exp = exponent; @@ -383,7 +396,8 @@ inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa // the actual digits. we then compare the big integer representations // of both, and use that to direct rounding. template -inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { +inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { // remove the invalid exponent bias am.power2 -= invalid_am_bias; diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index d3497fd..65704da 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -3,6 +3,8 @@ #include +#include "constexpr_feature_detect.h" + namespace fast_float { enum chars_format { scientific = 1<<0, @@ -48,6 +50,7 @@ struct parse_options { * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. */ template +FASTFLOAT_CONSTEXPR20 from_chars_result from_chars(const char *first, const char *last, T &value, chars_format fmt = chars_format::general) noexcept; @@ -55,6 +58,7 @@ from_chars_result from_chars(const char *first, const char *last, * Like from_chars, but accepts an `options` argument to govern number parsing. */ template +FASTFLOAT_CONSTEXPR20 from_chars_result from_chars_advanced(const char *first, const char *last, T &value, parse_options options) noexcept; diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 77e3e59..c878486 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -7,23 +7,8 @@ #include #include -#ifdef __has_include -#if __has_include() -#include -#endif -#endif - -#if __cpp_lib_bit_cast >= 201806L +#if FASTFLOAT_HAS_BIT_CAST #include -#define FASTFLOAT_HAS_BIT_CAST 1 -#else -#define FASTFLOAT_HAS_BIT_CAST 0 -#endif - -#if __cpp_lib_is_constant_evaluated >= 201811L -#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 -#else -#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 #endif #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ @@ -110,22 +95,6 @@ // rust style `try!()` macro, or `?` operator #define FASTFLOAT_TRY(x) { if (!(x)) return false; } -// Testing for https://wg21.link/N3652, adopted in C++14 -#if __cpp_constexpr >= 201304 -#define FASTFLOAT_CONSTEXPR14 constexpr -#else -#define FASTFLOAT_CONSTEXPR14 -#endif - -// Testing for relevant C++20 constexpr library features -#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \ - && FASTFLOAT_HAS_BIT_CAST \ - && __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ -#define FASTFLOAT_CONSTEXPR20 constexpr -#else -#define FASTFLOAT_CONSTEXPR20 -#endif - namespace fast_float { fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 5f5c264..d16a25d 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -20,8 +20,9 @@ namespace detail { * strings a null-free and fixed. **/ template -from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { - from_chars_result answer; +from_chars_result FASTFLOAT_CONSTEXPR14 +parse_infnan(const char *first, const char *last, T &value) noexcept { + from_chars_result answer{}; answer.ptr = first; answer.ec = std::errc(); // be optimistic bool minusSign = false; @@ -132,12 +133,14 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept { } // namespace detail template +FASTFLOAT_CONSTEXPR20 from_chars_result from_chars(const char *first, const char *last, T &value, chars_format fmt /*= chars_format::general*/) noexcept { return from_chars_advanced(first, last, value, parse_options{fmt}); } template +FASTFLOAT_CONSTEXPR20 from_chars_result from_chars_advanced(const char *first, const char *last, T &value, parse_options options) noexcept { @@ -174,7 +177,7 @@ from_chars_result from_chars_advanced(const char *first, const char *last, // We could check it first (before the previous branch), but // there might be performance advantages at having the check // be last. - if(detail::rounds_to_nearest()) { + if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { // We have that fegetround() == FE_TONEAREST. // Next is Clinger's fast path. if (pns.mantissa <=binary_format::max_mantissa_fast_path()) { @@ -191,7 +194,7 @@ from_chars_result from_chars_advanced(const char *first, const char *last, #if defined(__clang__) // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD if(pns.mantissa == 0) { - value = 0; + value = pns.negative ? -0. : 0.; return answer; } #endif diff --git a/script/amalgamate.py b/script/amalgamate.py index a5377d7..7ef44d4 100644 --- a/script/amalgamate.py +++ b/script/amalgamate.py @@ -31,7 +31,7 @@ for filename in ['LICENSE-MIT', 'LICENSE-APACHE']: processed_files[filename] = text # code -for filename in [ 'fast_float.h', 'float_common.h', 'ascii_number.h', +for filename in [ 'constexpr_feature_detect.h', 'fast_float.h', 'float_common.h', 'ascii_number.h', 'fast_table.h', 'decimal_to_binary.h', 'bigint.h', 'ascii_number.h', 'digit_comparison.h', 'parse_number.h']: with open('include/fast_float/' + filename, encoding='utf8') as f: @@ -75,6 +75,7 @@ def license_content(license_arg): text = ''.join([ processed_files['AUTHORS'], processed_files['CONTRIBUTORS'], *license_content(args.license), + processed_files['constexpr_feature_detect.h'], processed_files['fast_float.h'], processed_files['float_common.h'], processed_files['ascii_number.h'], processed_files['fast_table.h'], processed_files['decimal_to_binary.h'], processed_files['bigint.h'], diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 223c9e1..2dc784e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,7 +60,13 @@ fast_float_add_cpp_test(rcppfastfloat_test) fast_float_add_cpp_test(example_test) fast_float_add_cpp_test(example_comma_test) fast_float_add_cpp_test(basictest) -target_compile_features(basictest PRIVATE cxx_std_17) +option(FASTFLOAT_CONSTEXPR_TESTS "Constexpr tests" OFF) +if (FASTFLOAT_CONSTEXPR_TESTS) + target_compile_features(basictest PRIVATE cxx_std_20) + target_compile_definitions(basictest PRIVATE FASTFLOAT_CONSTEXPR_TESTS) +else() + target_compile_features(basictest PRIVATE cxx_std_17) +endif() fast_float_add_cpp_test(long_test) fast_float_add_cpp_test(powersoffive_hardround) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 433a9b5..5615b5d 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -10,8 +10,13 @@ #include #include #include +#include #include +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + #ifndef SUPPLEMENTAL_TEST_DATA_DIR #define SUPPLEMENTAL_TEST_DATA_DIR "data/" #endif @@ -184,6 +189,53 @@ TEST_CASE("parse_zero") { CHECK(float64_parsed == 0); } +TEST_CASE("parse_negative_zero") { + // + // If this function fails, we may be left in a non-standard rounding state. + // + const char * negative_zero = "-0"; + uint64_t float64_parsed; + double f = -0.; + ::memcpy(&float64_parsed, &f, sizeof(f)); + CHECK(float64_parsed == 0x8000'0000'0000'0000); + + fesetround(FE_UPWARD); + auto r1 = fast_float::from_chars(negative_zero, negative_zero + 2, f); + CHECK(r1.ec == std::errc()); + std::cout << "FE_UPWARD parsed negative zero as " << iHexAndDec(f) << std::endl; + CHECK(f == 0); + ::memcpy(&float64_parsed, &f, sizeof(f)); + std::cout << "double as uint64_t is " << float64_parsed << std::endl; + CHECK(float64_parsed == 0x8000'0000'0000'0000); + + fesetround(FE_TOWARDZERO); + auto r2 = fast_float::from_chars(negative_zero, negative_zero + 2, f); + CHECK(r2.ec == std::errc()); + std::cout << "FE_TOWARDZERO parsed negative zero as " << iHexAndDec(f) << std::endl; + CHECK(f == 0); + ::memcpy(&float64_parsed, &f, sizeof(f)); + std::cout << "double as uint64_t is " << float64_parsed << std::endl; + CHECK(float64_parsed == 0x8000'0000'0000'0000); + + fesetround(FE_DOWNWARD); + auto r3 = fast_float::from_chars(negative_zero, negative_zero + 2, f); + CHECK(r3.ec == std::errc()); + std::cout << "FE_DOWNWARD parsed negative zero as " << iHexAndDec(f) << std::endl; + CHECK(f == 0); + ::memcpy(&float64_parsed, &f, sizeof(f)); + std::cout << "double as uint64_t is " << float64_parsed << std::endl; + CHECK(float64_parsed == 0x8000'0000'0000'0000); + + fesetround(FE_TONEAREST); + auto r4 = fast_float::from_chars(negative_zero, negative_zero + 2, f); + CHECK(r4.ec == std::errc()); + std::cout << "FE_TONEAREST parsed negative zero as " << iHexAndDec(f) << std::endl; + CHECK(f == 0); + ::memcpy(&float64_parsed, &f, sizeof(f)); + std::cout << "double as uint64_t is " << float64_parsed << std::endl; + CHECK(float64_parsed == 0x8000'0000'0000'0000); +} + // C++ 17 because it is otherwise annoying to browse all files in a directory. // We also only run these tests on little endian systems. #if (FASTFLOAT_CPLUSPLUS >= 201703L) && (FASTFLOAT_IS_BIG_ENDIAN == 0) && !defined(FASTFLOAT_ODDPLATFORM) @@ -572,52 +624,146 @@ std::string append_zeros(std::string str, size_t number_of_zeros) { return answer; } -template -void check_basic_test_result(const std::string& str, fast_float::from_chars_result result, - T actual, T expected) { - INFO("str=" << str << "\n" - << " expected=" << fHexAndDec(expected) << "\n" - << " ..actual=" << fHexAndDec(actual) << "\n" - << " expected mantissa=" << iHexAndDec(get_mantissa(expected)) << "\n" - << " ..actual mantissa=" << iHexAndDec(get_mantissa(actual))); - CHECK_EQ(result.ec, std::errc()); - CHECK_EQ(result.ptr, str.data() + str.size()); - CHECK_EQ(copysign(1, actual), copysign(1, expected)); - CHECK_EQ(std::isnan(actual), std::isnan(expected)); - CHECK_EQ(actual, expected); +namespace { + +enum class Diag { runtime, comptime }; + +} // anonymous namespace + +template +constexpr void check_basic_test_result(std::string_view str, + fast_float::from_chars_result result, + T actual, T expected) { + if constexpr (diag == Diag::runtime) { + INFO( + "str=" << str << "\n" + << " expected=" << fHexAndDec(expected) << "\n" + << " ..actual=" << fHexAndDec(actual) << "\n" + << " expected mantissa=" << iHexAndDec(get_mantissa(expected)) + << "\n" + << " ..actual mantissa=" << iHexAndDec(get_mantissa(actual))); + } + + struct ComptimeDiag { + // Purposely not constexpr + static void error_not_equal() {} + }; + +#define FASTFLOAT_CHECK_EQ(...) \ + if constexpr (diag == Diag::runtime) { \ + CHECK_EQ(__VA_ARGS__); \ + } else { \ + if ([](const auto &lhs, const auto &rhs) { \ + return lhs != rhs; \ + }(__VA_ARGS__)) { \ + ComptimeDiag::error_not_equal(); \ + } \ + } + + auto copysign = [](double x, double y) -> double { +#if FASTFLOAT_HAS_BIT_CAST + if (fast_float::cpp20_and_in_constexpr()) { + using equiv_int = std::make_signed_t< + typename fast_float::binary_format::equiv_uint>; + const auto i = std::bit_cast(y); + if (i < 0) { + return -x; + } + return x; + } +#endif + return std::copysign(x, y); + }; + + auto isnan = [](double x) -> bool { + return x != x; + }; + + FASTFLOAT_CHECK_EQ(result.ec, std::errc()); + FASTFLOAT_CHECK_EQ(result.ptr, str.data() + str.size()); + FASTFLOAT_CHECK_EQ(copysign(1, actual), copysign(1, expected)); + FASTFLOAT_CHECK_EQ(isnan(actual), isnan(expected)); + FASTFLOAT_CHECK_EQ(actual, expected); + +#undef FASTFLOAT_CHECK_EQ } -template -void basic_test(std::string str, T expected) { +template +constexpr void basic_test(std::string_view str, T expected) { T actual; auto result = fast_float::from_chars(str.data(), str.data() + str.size(), actual); - check_basic_test_result(str, result, actual, expected); + check_basic_test_result(str, result, actual, expected); } -template -void basic_test(std::string str, T expected, fast_float::parse_options options) { +template +constexpr void basic_test(std::string_view str, T expected, fast_float::parse_options options) { T actual; auto result = fast_float::from_chars_advanced(str.data(), str.data() + str.size(), actual, options); - check_basic_test_result(str, result, actual, expected); + check_basic_test_result(str, result, actual, expected); } void basic_test(float val) { { std::string long_vals = to_long_string(val); INFO("long vals: " << long_vals); - basic_test(long_vals, val); + basic_test(long_vals, val); } { std::string vals = to_string(val); INFO("vals: " << vals); - basic_test(vals, val); + basic_test(vals, val); } } -#define verify(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs); } -#define verify32(val) { INFO(#val); basic_test(val); } +#define verify_runtime(lhs, rhs) \ + do { \ + INFO(lhs); \ + basic_test(lhs, rhs); \ + } while (false) -#define verify_options(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs, options); } +#define verify_comptime(lhs, rhs) \ + do { \ + constexpr int verify_comptime_var = \ + (basic_test(lhs, rhs), 0); \ + (void)verify_comptime_var; \ + } while (false) + +#define verify_options_runtime(lhs, rhs) \ + do { \ + INFO(lhs); \ + basic_test(lhs, rhs, options); \ + } while (false) + +#define verify_options_comptime(lhs, rhs) \ + do { \ + constexpr int verify_options_comptime_var = \ + (basic_test(lhs, rhs, options), 0); \ + (void)verify_options_comptime_var; \ + } while (false) + +#if defined(FASTFLOAT_CONSTEXPR_TESTS) +#if !FASTFLOAT_IS_CONSTEXPR +#error "from_chars must be constexpr for constexpr tests" +#endif + +#define verify(lhs, rhs) \ + do { \ + verify_runtime(lhs, rhs); \ + verify_comptime(lhs, rhs); \ + } while (false) + +#define verify_options(lhs, rhs) \ + do { \ + verify_options_runtime(lhs, rhs); \ + verify_options_comptime(lhs, rhs); \ + } while (false) + +#else +#define verify verify_runtime +#define verify_options verify_options_runtime +#endif + +#define verify32(val) { INFO(#val); basic_test(val); } TEST_CASE("64bit.inf") { verify("INF", std::numeric_limits::infinity()); @@ -644,7 +790,7 @@ TEST_CASE("64bit.general") { verify("-2.2222222222223e-322",-0x1.68p-1069); verify("9007199254740993.0", 0x1p+53); verify("860228122.6654514319E+90", 0x1.92bb20990715fp+328); - verify(append_zeros("9007199254740993.0",1000), 0x1p+53); + verify_runtime(append_zeros("9007199254740993.0",1000), 0x1p+53); verify("10000000000000000000", 0x1.158e460913dp+63); verify("10000000000000000000000000000001000000000000", 0x1.cb2d6f618c879p+142); verify("10000000000000000000000000000000000000000001", 0x1.cb2d6f618c879p+142); @@ -701,8 +847,11 @@ TEST_CASE("64bit.general") { } TEST_CASE("64bit.decimal_point") { - fast_float::parse_options options{}; - options.decimal_point = ','; + constexpr auto options = []{ + fast_float::parse_options ret{}; + ret.decimal_point = ','; + return ret; + }(); // infinities verify_options("1,8e308", std::numeric_limits::infinity()); @@ -715,7 +864,7 @@ TEST_CASE("64bit.decimal_point") { verify_options("-2,2222222222223e-322",-0x1.68p-1069); verify_options("9007199254740993,0", 0x1p+53); verify_options("860228122,6654514319E+90", 0x1.92bb20990715fp+328); - verify_options(append_zeros("9007199254740993,0",1000), 0x1p+53); + verify_options_runtime(append_zeros("9007199254740993,0",1000), 0x1p+53); verify_options("1,1920928955078125e-07", 1.1920928955078125e-07); verify_options("9355950000000000000,00000000000000000000000000000000001844674407370955161600000184467440737095516161844674407370955161407370955161618446744073709551616000184467440737095516166000001844674407370955161618446744073709551614073709551616184467440737095516160001844674407370955161601844674407370955674451616184467440737095516140737095516161844674407370955161600018446744073709551616018446744073709551611616000184467440737095001844674407370955161600184467440737095516160018446744073709551168164467440737095516160001844073709551616018446744073709551616184467440737095516160001844674407536910751601611616000184467440737095001844674407370955161600184467440737095516160018446744073709551616184467440737095516160001844955161618446744073709551616000184467440753691075160018446744073709",0x1.03ae05e8fca1cp+63); verify_options("2,22507385850720212418870147920222032907240528279439037814303133837435107319244194686754406432563881851382188218502438069999947733013005649884107791928741341929297200970481951993067993290969042784064731682041565926728632933630474670123316852983422152744517260835859654566319282835244787787799894310779783833699159288594555213714181128458251145584319223079897504395086859412457230891738946169368372321191373658977977723286698840356390251044443035457396733706583981055420456693824658413747607155981176573877626747665912387199931904006317334709003012790188175203447190250028061277777916798391090578584006464715943810511489154282775041174682194133952466682503431306181587829379004205392375072083366693241580002758391118854188641513168478436313080237596295773983001708984375e-308", 0x1.0000000000002p-1022); @@ -778,16 +927,16 @@ TEST_CASE("32bit.general") { verify("-1e-999",-0.0f); verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f); verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f); - verify(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); - verify(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f); - verify(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f); + verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); + verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f); + verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f); std::string test_string; test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655) + std::string("e-38"); - verify(test_string, 0x1.fffff8p-127f); + verify_runtime(test_string, 0x1.fffff8p-127f); test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656) + std::string("e-38"); - verify(test_string, 0x1.fffff8p-127f); + verify_runtime(test_string, 0x1.fffff8p-127f); test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000) + std::string("e-38"); - verify(test_string, 0x1.fffff8p-127f); + verify_runtime(test_string, 0x1.fffff8p-127f); verify32(1.00000006e+09f); verify32(1.4012984643e-45f); verify32(1.1754942107e-38f); @@ -852,8 +1001,11 @@ TEST_CASE("32bit.general") { } TEST_CASE("32bit.decimal_point") { - fast_float::parse_options options{}; - options.decimal_point = ','; + constexpr auto options = [] { + fast_float::parse_options ret{}; + ret.decimal_point = ','; + return ret; + }(); // infinity verify_options("3,5028234666e38", std::numeric_limits::infinity()); @@ -861,16 +1013,16 @@ TEST_CASE("32bit.decimal_point") { // finites verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f); verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f); - verify_options(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); - verify_options(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f); - verify_options(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f); + verify_options_runtime(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); + verify_options_runtime(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f); + verify_options_runtime(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f); std::string test_string; test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655) + std::string("e-38"); - verify_options(test_string, 0x1.fffff8p-127f); + verify_options_runtime(test_string, 0x1.fffff8p-127f); test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656) + std::string("e-38"); - verify_options(test_string, 0x1.fffff8p-127f); + verify_options_runtime(test_string, 0x1.fffff8p-127f); test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000) + std::string("e-38"); - verify_options(test_string, 0x1.fffff8p-127f); + verify_options_runtime(test_string, 0x1.fffff8p-127f); verify_options("1,1754943508e-38", 1.1754943508e-38f); verify_options("30219,0830078125", 30219.0830078125f); verify_options("1,1754947011469036e-38", 0x1.000006p-126f); diff --git a/tests/long_random64.cpp b/tests/long_random64.cpp index a6680e8..713c125 100644 --- a/tests/long_random64.cpp +++ b/tests/long_random64.cpp @@ -62,6 +62,7 @@ void random_values(size_t N) { if (errors > 10) { abort(); } + continue; } if (std::isnan(v)) { if (!std::isnan(result_value)) { diff --git a/tests/random64.cpp b/tests/random64.cpp index 0775993..6b3ef50 100644 --- a/tests/random64.cpp +++ b/tests/random64.cpp @@ -65,6 +65,7 @@ void random_values(size_t N) { if (errors > 10) { abort(); } + continue; } if (std::isnan(v)) { if (!std::isnan(result_value)) {