diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index db9dcce..008ce17 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -6,6 +6,11 @@ #include #include #include +#include + +#if defined(__cpp_lib_bit_cast) +#include +#endif #include "float_common.h" @@ -13,9 +18,9 @@ namespace fast_float { // Next function can be micro-optimized, but compilers are entirely // able to optimize it well. -fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } +CXX20_CONSTEXPR fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } -fastfloat_really_inline uint64_t byteswap(uint64_t val) { +CXX20_CONSTEXPR fastfloat_really_inline uint64_t byteswap(uint64_t val) { return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | (val & 0x0000FF0000000000) >> 24 @@ -26,9 +31,13 @@ fastfloat_really_inline uint64_t byteswap(uint64_t val) { | (val & 0x00000000000000FF) << 56; } -fastfloat_really_inline uint64_t read_u64(const char *chars) { +CXX20_CONSTEXPR fastfloat_really_inline uint64_t read_u64(const char *chars) { uint64_t val; +#if defined(__cpp_lib_bit_cast) + val = std::bit_cast(reinterpret_cast(chars)); +#else ::memcpy(&val, chars, sizeof(uint64_t)); +#endif #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); @@ -36,16 +45,26 @@ fastfloat_really_inline uint64_t read_u64(const char *chars) { return val; } -fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +CXX20_CONSTEXPR fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif +#if defined(__cpp_lib_bit_cast) + if (std::is_constant_evaluated()) { + char (&dst)[8] = reinterpret_cast(chars); + const char (&src)[8] = reinterpret_cast(val); + std::copy(std::begin(src), std::end(src), std::begin(dst)); + } else { + ::memcpy(chars, &val, sizeof(uint64_t)); + } +#else ::memcpy(chars, &val, sizeof(uint64_t)); +#endif } // credit @aqrit -fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { +CXX20_CONSTEXPR fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { const uint64_t mask = 0x000000FF000000FF; const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) @@ -55,17 +74,17 @@ fastfloat_really_inline 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 { +CXX20_CONSTEXPR fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { return parse_eight_digits_unrolled(read_u64(chars)); } // credit @aqrit -fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { +CXX20_CONSTEXPR fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); } -fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { +CXX20_CONSTEXPR fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { return is_made_of_eight_digits_fast(read_u64(chars)); } @@ -81,7 +100,7 @@ struct parsed_number_string { // Assuming that you use no more than 19 digits, this will // parse an ASCII string. -fastfloat_really_inline +CXX20_CONSTEXPR fastfloat_really_inline 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; @@ -221,7 +240,7 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_ // This function could be optimized. In particular, we could stop after 19 digits // and try to bail out. Furthermore, we should be able to recover the computed // exponent from the pass in parse_number_string. -fastfloat_really_inline decimal parse_decimal(const char *p, const char *pend, parse_options options) noexcept { +CXX20_CONSTEXPR fastfloat_really_inline decimal parse_decimal(const char *p, const char *pend, parse_options options) noexcept { const char decimal_point = options.decimal_point; decimal answer; diff --git a/include/fast_float/decimal_to_binary.h b/include/fast_float/decimal_to_binary.h index 687b834..5f9a8df 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -18,7 +18,7 @@ namespace fast_float { // low part corresponding to the least significant bits. // template -fastfloat_really_inline +CXX20_CONSTEXPR fastfloat_really_inline 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 @@ -56,7 +56,7 @@ namespace detail { * where * p = log(5**-q)/log(2) = -q * log(5)/log(2) */ - fastfloat_really_inline int power(int q) noexcept { + constexpr fastfloat_really_inline int power(int q) noexcept { return (((152170 + 65536) * q) >> 16) + 63; } } // namespace detail @@ -68,7 +68,7 @@ namespace detail { // return an adjusted_mantissa with a negative power of 2: the caller should recompute // in such cases. template -fastfloat_really_inline +CXX20_CONSTEXPR fastfloat_really_inline 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/fast_float.h b/include/fast_float/fast_float.h index a4fb8b0..b22a344 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -1,7 +1,15 @@ #ifndef FASTFLOAT_FAST_FLOAT_H #define FASTFLOAT_FAST_FLOAT_H + #include +#include + +#if defined(__cpp_lib_bit_cast) +#define CXX20_CONSTEXPR constexpr +#else +#define CXX20_CONSTEXPR +#endif namespace fast_float { enum chars_format { @@ -18,7 +26,7 @@ struct from_chars_result { }; struct parse_options { - explicit parse_options(chars_format fmt = chars_format::general, + constexpr explicit parse_options(chars_format fmt = chars_format::general, char dot = '.') : format(fmt), decimal_point(dot) {} @@ -48,14 +56,14 @@ struct parse_options { * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. */ template -from_chars_result from_chars(const char *first, const char *last, +CXX20_CONSTEXPR from_chars_result from_chars(const char *first, const char *last, T &value, chars_format fmt = chars_format::general) noexcept; /** * Like from_chars, but accepts an `options` argument to govern number parsing. */ template -from_chars_result from_chars_advanced(const char *first, const char *last, +CXX20_CONSTEXPR 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 1bb9456..b07ef43 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -4,6 +4,7 @@ #include #include #include +#include #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ @@ -73,10 +74,18 @@ #define fastfloat_really_inline inline __attribute__((always_inline)) #endif +#if !defined(CXX20_CONSTEXPR) +#if defined(__cpp_lib_bit_cast) +#define CXX20_CONSTEXPR constexpr +#else +#define CXX20_CONSTEXPR +#endif +#endif + namespace fast_float { // Compares two ASCII strings in a case insensitive manner. -inline bool fastfloat_strncasecmp(const char *input1, const char *input2, +CXX20_CONSTEXPR inline bool fastfloat_strncasecmp(const char *input1, const char *input2, size_t length) { char running_diff{0}; for (size_t i = 0; i < length; i++) { @@ -103,7 +112,7 @@ struct value128 { }; /* result might be undefined when input_num is zero */ -fastfloat_really_inline int leading_zeroes(uint64_t input_num) { +CXX20_CONSTEXPR fastfloat_really_inline int leading_zeroes(uint64_t input_num) { assert(input_num > 0); #ifdef FASTFLOAT_VISUAL_STUDIO #if defined(_M_X64) || defined(_M_ARM64) @@ -130,13 +139,13 @@ fastfloat_really_inline int leading_zeroes(uint64_t input_num) { #ifdef FASTFLOAT_32BIT // slow emulation routine for 32-bit -fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { +CXX20_CONSTEXPR fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { return x * (uint64_t)y; } // slow emulation routine for 32-bit #if !defined(__MINGW64__) -fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, +CXX20_CONSTEXPR fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 5918a9e..85ee164 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -20,7 +20,7 @@ namespace detail { * strings a null-free and fixed. **/ template -from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { +CXX20_CONSTEXPR from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { from_chars_result answer; answer.ptr = first; answer.ec = std::errc(); // be optimistic @@ -61,7 +61,7 @@ from_chars_result parse_infnan(const char *first, const char *last, T &value) n } template -fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { +CXX20_CONSTEXPR fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { uint64_t word = am.mantissa; word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); word = negative @@ -83,13 +83,13 @@ fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &va template -from_chars_result from_chars(const char *first, const char *last, +CXX20_CONSTEXPR 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 -from_chars_result from_chars_advanced(const char *first, const char *last, +CXX20_CONSTEXPR from_chars_result from_chars_advanced(const char *first, const char *last, T &value, parse_options options) noexcept { static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); diff --git a/include/fast_float/simple_decimal_conversion.h b/include/fast_float/simple_decimal_conversion.h index 81ff0e9..5310bf5 100644 --- a/include/fast_float/simple_decimal_conversion.h +++ b/include/fast_float/simple_decimal_conversion.h @@ -22,7 +22,7 @@ namespace fast_float { namespace detail { // remove all final zeroes -inline void trim(decimal &h) { +CXX20_CONSTEXPR inline void trim(decimal &h) { while ((h.num_digits > 0) && (h.digits[h.num_digits - 1] == 0)) { h.num_digits--; } @@ -30,9 +30,9 @@ inline void trim(decimal &h) { -inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t shift) { +CXX20_CONSTEXPR inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t shift) { shift &= 63; - const static uint16_t number_of_digits_decimal_left_shift_table[65] = { + constexpr uint16_t number_of_digits_decimal_left_shift_table[65] = { 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, @@ -47,7 +47,7 @@ inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t s uint32_t num_new_digits = x_a >> 11; uint32_t pow5_a = 0x7FF & x_a; uint32_t pow5_b = 0x7FF & x_b; - const static uint8_t + constexpr uint8_t number_of_digits_decimal_left_shift_table_powers_of_5[0x051C] = { 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, @@ -123,7 +123,7 @@ inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t s return num_new_digits; } -inline uint64_t round(decimal &h) { +CXX20_CONSTEXPR inline uint64_t round(decimal &h) { if ((h.num_digits == 0) || (h.decimal_point < 0)) { return 0; } else if (h.decimal_point > 18) { @@ -150,7 +150,7 @@ inline uint64_t round(decimal &h) { } // computes h * 2^-shift -inline void decimal_left_shift(decimal &h, uint32_t shift) { +CXX20_CONSTEXPR inline void decimal_left_shift(decimal &h, uint32_t shift) { if (h.num_digits == 0) { return; } @@ -192,7 +192,7 @@ inline void decimal_left_shift(decimal &h, uint32_t shift) { } // computes h * 2^shift -inline void decimal_right_shift(decimal &h, uint32_t shift) { +CXX20_CONSTEXPR inline void decimal_right_shift(decimal &h, uint32_t shift) { uint32_t read_index = 0; uint32_t write_index = 0; @@ -241,7 +241,7 @@ inline void decimal_right_shift(decimal &h, uint32_t shift) { } // namespace detail template -adjusted_mantissa compute_float(decimal &d) { +CXX20_CONSTEXPR adjusted_mantissa compute_float(decimal &d) { adjusted_mantissa answer; if (d.num_digits == 0) { // should be zero @@ -271,9 +271,9 @@ adjusted_mantissa compute_float(decimal &d) { answer.mantissa = 0; return answer; } - static const uint32_t max_shift = 60; - static const uint32_t num_powers = 19; - static const uint8_t decimal_powers[19] = { + constexpr uint32_t max_shift = 60; + constexpr uint32_t num_powers = 19; + constexpr uint8_t decimal_powers[19] = { 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // 33, 36, 39, 43, 46, 49, 53, 56, 59, // }; @@ -351,7 +351,7 @@ adjusted_mantissa compute_float(decimal &d) { } template -adjusted_mantissa parse_long_mantissa(const char *first, const char* last, parse_options options) { +CXX20_CONSTEXPR adjusted_mantissa parse_long_mantissa(const char *first, const char* last, parse_options options) { decimal d = parse_decimal(first, last, options); return compute_float(d); } diff --git a/tests/basictest.cpp b/tests/basictest.cpp index bfbc320..85bb276 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -5,6 +5,7 @@ #include "fast_float/fast_float.h" #include #include +#include #ifndef SUPPLEMENTAL_TEST_DATA_DIR #define SUPPLEMENTAL_TEST_DATA_DIR "data/" @@ -37,6 +38,11 @@ #define FASTFLOAT_ODDPLATFORM 1 #endif +#if defined(__cpp_lib_bit_cast) +#include +#include +#endif + // 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) @@ -101,6 +107,7 @@ bool check_file(std::string file_name) { return true; } + TEST_CASE("supplemental") { std::string path = SUPPLEMENTAL_TEST_DATA_DIR; for (const auto & entry : std::filesystem::directory_iterator(path)) { @@ -109,6 +116,27 @@ TEST_CASE("supplemental") { } #endif +#if defined(__cpp_lib_bit_cast) + +constexpr double tryParse(std::string_view input) +{ + double result{}; + fast_float::from_chars_result parseResult = fast_float::from_chars(input.data(), input.data() + input.size(), result); + if (parseResult.ec != std::errc()) { + return std::numeric_limits::quiet_NaN(); + } + return result; +} + +static_assert(tryParse("1.0") == 1.0); +static_assert(tryParse("2.0") == 2.0); +static_assert(tryParse("3.14156") == 3.14156); +static_assert(tryParse("3.14156") != 3.1415600000001); +#if !defined(_MSVC_LANG) +static_assert(std::isnan(tryParse("hellothere"))); // technically isnan is not constexpr but GCC and clang allow it +#endif + +#endif //#if defined(__cpp_lib_bit_cast) TEST_CASE("leading_zeroes") { constexpr const uint64_t bit = 1;