From 82ee3b1b5f2a7cef80336c64ca82d152ec9af72b Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 4 Mar 2023 16:49:34 +0000 Subject: [PATCH 01/13] Constexpr parse_number_string --- include/fast_float/ascii_number.h | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) 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; From e4d4e43b21276b2014b090c898f796e0b59ed64d Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 4 Mar 2023 17:16:45 +0000 Subject: [PATCH 02/13] Constexpr from_chars --- include/fast_float/decimal_to_binary.h | 6 ++-- include/fast_float/digit_comparison.h | 42 +++++++++++++++++--------- include/fast_float/fast_float.h | 4 +++ include/fast_float/parse_number.h | 9 ++++-- 4 files changed, 41 insertions(+), 20 deletions(-) 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..12ec693 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -3,6 +3,8 @@ #include +#include "float_common.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/parse_number.h b/include/fast_float/parse_number.h index 3284443..4f330ac 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; @@ -127,12 +128,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 { @@ -169,7 +172,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()) { From 58798ee81f478a0d56d008b79a54f80193781e6f Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 4 Mar 2023 17:23:20 +0000 Subject: [PATCH 03/13] Fix failing test builds with -Werror=maybe-uninitialized on gcc Apparently the added constexpr makes gcc's control flow analysis to go deeper. If from_chars returns with error then the out parameter remains uninitialized. As it's undefined behavior to use its value, it's better to just skip the rest of the loop in this case. --- tests/long_random64.cpp | 1 + tests/random64.cpp | 1 + 2 files changed, 2 insertions(+) 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)) { From 6732e397d829b82a08c082508a0cafccac45dcac Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 4 Mar 2023 22:36:58 +0000 Subject: [PATCH 04/13] Add constexpr testing When enabled, modify `verify` macro to also verify at compile time, when the arguments are constant expressions. --- include/fast_float/float_common.h | 2 ++ tests/CMakeLists.txt | 8 ++++++- tests/basictest.cpp | 38 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 77e3e59..468458d 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -122,8 +122,10 @@ && 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 namespace fast_float { 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..4fe391f 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #ifndef SUPPLEMENTAL_TEST_DATA_DIR @@ -614,7 +615,44 @@ void basic_test(float val) { } } +namespace { +template +struct dummy {}; + +template +struct verify_error; +} //anonymous namespace + +#if defined(FASTFLOAT_CONSTEXPR_TESTS) +#if !FASTFLOAT_IS_CONSTEXPR +#error "from_chars must be constexpr for constexpr tests" +#endif + +// Add constexpr testing to verify when the arguments are constant expressions +#define verify(lhs, rhs) \ + { \ + INFO(lhs); \ + basic_test(lhs, rhs); \ + [&]() { \ + if constexpr (requires { \ + typename ::dummy<(T(lhs), 0)>; \ + typename ::dummy<((void)(rhs), 0)>; \ + }) { \ + constexpr auto sv = T(lhs); \ + constexpr auto val = [&] { \ + ::std::remove_cvref_t ret; \ + (void)::fast_float::from_chars(sv.data(), sv.data() + sv.size(), \ + ret); \ + return ret; \ + }(); \ + static_assert(val == (rhs)); \ + } \ + }.operator()<::std::string_view>(); \ + } +#else #define verify(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs); } +#endif + #define verify32(val) { INFO(#val); basic_test(val); } #define verify_options(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs, options); } From e05858a0f8dc3605429354590d091963a0f7fa16 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 4 Mar 2023 23:02:15 +0000 Subject: [PATCH 05/13] Work around clang bug. https://godbolt.org/z/zedh7rrhc This is similar to https://github.com/llvm/llvm-project/issues/47746, except I needed to use a different workaround. --- include/fast_float/bigint.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/fast_float/bigint.h b/include/fast_float/bigint.h index 5a72caa..3ad7315 100644 --- a/include/fast_float/bigint.h +++ b/include/fast_float/bigint.h @@ -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; From ffc3fd7cc78774f1f3922262879a25cdb88058e6 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 4 Mar 2023 23:17:27 +0000 Subject: [PATCH 06/13] Fix amalgamate.py script This unfortunately puts fast_float.h as the second header, possiby making the amalgamated header less readable. --- script/amalgamate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/amalgamate.py b/script/amalgamate.py index a5377d7..7d4e4aa 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 [ 'float_common.h', 'fast_float.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,7 +75,7 @@ def license_content(license_arg): text = ''.join([ processed_files['AUTHORS'], processed_files['CONTRIBUTORS'], *license_content(args.license), - processed_files['fast_float.h'], processed_files['float_common.h'], + processed_files['float_common.h'], processed_files['fast_float.h'], processed_files['ascii_number.h'], processed_files['fast_table.h'], processed_files['decimal_to_binary.h'], processed_files['bigint.h'], processed_files['ascii_number.h'], processed_files['digit_comparison.h'], From d34d0d7405f0c913d9ed4907587402c8b6143c55 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sun, 12 Mar 2023 10:30:49 +0000 Subject: [PATCH 07/13] Fix amalgamate.ph and add header for constexpr macros --- include/fast_float/constexpr_feature_detect.h | 40 +++++++++++++++++++ include/fast_float/fast_float.h | 2 +- include/fast_float/float_common.h | 35 +--------------- script/amalgamate.py | 5 ++- 4 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 include/fast_float/constexpr_feature_detect.h 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/fast_float.h b/include/fast_float/fast_float.h index 12ec693..65704da 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -3,7 +3,7 @@ #include -#include "float_common.h" +#include "constexpr_feature_detect.h" namespace fast_float { enum chars_format { diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 468458d..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,24 +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 -#define FASTFLOAT_IS_CONSTEXPR 1 -#else -#define FASTFLOAT_CONSTEXPR20 -#define FASTFLOAT_IS_CONSTEXPR 0 -#endif - namespace fast_float { fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { diff --git a/script/amalgamate.py b/script/amalgamate.py index 7d4e4aa..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 [ 'float_common.h', 'fast_float.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,7 +75,8 @@ def license_content(license_arg): text = ''.join([ processed_files['AUTHORS'], processed_files['CONTRIBUTORS'], *license_content(args.license), - processed_files['float_common.h'], processed_files['fast_float.h'], + 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'], processed_files['ascii_number.h'], processed_files['digit_comparison.h'], From 612a7bf5eb124c16b446b1b5cc62e5576a8fc849 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sun, 12 Mar 2023 11:16:08 +0000 Subject: [PATCH 08/13] Enable constexpr tests for VS17 c++20 build. --- .github/workflows/vs17-cxx20.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 From 264414c0b0760736561915dc54dfba6e9356c365 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sun, 12 Mar 2023 15:17:14 +0000 Subject: [PATCH 09/13] Remove default capture with init-capture in hope of fixing MSVC build. --- tests/basictest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 4fe391f..2f3f771 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -639,7 +639,7 @@ struct verify_error; typename ::dummy<((void)(rhs), 0)>; \ }) { \ constexpr auto sv = T(lhs); \ - constexpr auto val = [&] { \ + constexpr auto val = [&sv=sv] { \ ::std::remove_cvref_t ret; \ (void)::fast_float::from_chars(sv.data(), sv.data() + sv.size(), \ ret); \ From 24b2fdaad0e330231f47a48a25f4a2e14d3ef7aa Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Fri, 24 Mar 2023 16:18:00 +0000 Subject: [PATCH 10/13] Separate verify and verify_runtime macros, extend constexpr checks `verify` runs both runtime and constexpr checks if the constexpr checks are enabled in cmake. `verify_runtime` only runs the runtime checks. --- tests/basictest.cpp | 149 ++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 55 deletions(-) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 2f3f771..ace299c 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -13,6 +13,10 @@ #include #include +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + #ifndef SUPPLEMENTAL_TEST_DATA_DIR #define SUPPLEMENTAL_TEST_DATA_DIR "data/" #endif @@ -573,55 +577,105 @@ 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); + }; + + 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(std::isnan(actual), std::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); } } -namespace { -template -struct dummy {}; +#define verify_runtime(lhs, rhs) \ + do { \ + INFO(lhs); \ + basic_test(lhs, rhs); \ + } while (false) -template -struct verify_error; -} //anonymous namespace +#define verify_comptime(lhs, rhs) \ + do { \ + constexpr int verify_comptime_var = \ + (basic_test(lhs, rhs), 0); \ + (void)verify_comptime_var; \ + } while (false) #if defined(FASTFLOAT_CONSTEXPR_TESTS) #if !FASTFLOAT_IS_CONSTEXPR @@ -630,32 +684,17 @@ struct verify_error; // Add constexpr testing to verify when the arguments are constant expressions #define verify(lhs, rhs) \ - { \ - INFO(lhs); \ - basic_test(lhs, rhs); \ - [&]() { \ - if constexpr (requires { \ - typename ::dummy<(T(lhs), 0)>; \ - typename ::dummy<((void)(rhs), 0)>; \ - }) { \ - constexpr auto sv = T(lhs); \ - constexpr auto val = [&sv=sv] { \ - ::std::remove_cvref_t ret; \ - (void)::fast_float::from_chars(sv.data(), sv.data() + sv.size(), \ - ret); \ - return ret; \ - }(); \ - static_assert(val == (rhs)); \ - } \ - }.operator()<::std::string_view>(); \ - } + do { \ + verify_runtime(lhs, rhs); \ + verify_comptime(lhs, rhs); \ + } while (false) #else -#define verify(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs); } +#define verify verify_runtime #endif #define verify32(val) { INFO(#val); basic_test(val); } -#define verify_options(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs, options); } +#define verify_options(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs, options); } TEST_CASE("64bit.inf") { verify("INF", std::numeric_limits::infinity()); @@ -682,7 +721,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); @@ -816,16 +855,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); From 6bd1e776b21415e5144d54a46234483896ca1905 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Fri, 24 Mar 2023 16:35:25 +0000 Subject: [PATCH 11/13] Constexpr isnan --- tests/basictest.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index ace299c..8a70242 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -628,10 +628,14 @@ constexpr void check_basic_test_result(std::string_view str, 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(std::isnan(actual), std::isnan(expected)); + FASTFLOAT_CHECK_EQ(isnan(actual), isnan(expected)); FASTFLOAT_CHECK_EQ(actual, expected); #undef FASTFLOAT_CHECK_EQ From e464bd77853c137aeaff388a3b9f578e35b74b36 Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Fri, 24 Mar 2023 17:18:58 +0000 Subject: [PATCH 12/13] Replace memmove with copy_backward --- include/fast_float/bigint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fast_float/bigint.h b/include/fast_float/bigint.h index 3ad7315..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; From a1a7c4e3e0ebadd817e861664ba8b8fdfb9f92ec Mon Sep 17 00:00:00 2001 From: Lenard Szolnoki Date: Sat, 25 Mar 2023 18:28:00 +0000 Subject: [PATCH 13/13] Extend verify_options with constexpr test --- tests/basictest.cpp | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 8a70242..fe5ccef 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -681,25 +681,43 @@ void basic_test(float val) { (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 -// Add constexpr testing to verify when the arguments are constant expressions #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); } -#define verify_options(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs, options); } - TEST_CASE("64bit.inf") { verify("INF", std::numeric_limits::infinity()); verify("-INF", -std::numeric_limits::infinity()); @@ -782,8 +800,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()); @@ -796,7 +817,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); @@ -933,8 +954,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()); @@ -942,16 +966,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);