diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4b70555..400c89f 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -9,3 +9,4 @@ Jan Pharago Maya Warrier Taha Khokhar Anders Dalvander +Elle Solomina diff --git a/README.md b/README.md index c554d37..a0a792e 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,48 @@ int main() { } ``` +You also can use not standard options: + +```C++ +#include "fast_float/fast_float.h" +#include + +int main() { + std::string input = " +456"; + double result; + fast_float::parse_options options{chars_format::allow_leading_plus | chars_format::skip_white_space}; + auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options); + if ((answer.ec != std::errc()) || ((result != 456))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} +``` + +For special case scenarious, like mathematical or other AST like parcer that already process minus sign +and only pasre in FastFloat positive numbers in fixed, scientific or hex format and do not have inf or nan +in input you can use macros FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN that significantly reduce +the code size and improve performance: + +```C++ +#define FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN +#include "fast_float/fast_float.h" +#include + +int main() { + std::string input = "23.14069263277926900572"; + double result; + auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result); + if ((answer.ec != std::errc()) || ((result != 23.14069263277927 /*properly rounded value */))) + { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + input = "-23.14069263277926900572"; + if (answer.ec == std::errc()) { std::cerr << "parsing failure, should failed on any sign\n"; return EXIT_FAILURE; } + input = "inf"; + if (answer.ec == std::errc()) { std::cerr << "parsing failure, should failed on infinity\n"; return EXIT_FAILURE; } + input = "nan"; + if (answer.ec == std::errc()) { std::cerr << "parsing failure, should failed on nan in input\n"; return EXIT_FAILURE; } + return EXIT_SUCCESS; +} +``` + ## Users and Related Work The fast_float library is part of: diff --git a/benchmarks/benchmark.cpp b/benchmarks/benchmark.cpp index 05f1233..eaa7d6c 100644 --- a/benchmarks/benchmark.cpp +++ b/benchmarks/benchmark.cpp @@ -1,10 +1,12 @@ + +// #define FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN +// #define FASTFLOAT_ONLY_ROUNDS_TO_NEAREST_SUPPORTED + #if defined(__linux__) || (__APPLE__ && __aarch64__) #define USING_COUNTERS #endif #include "event_counter.h" #include -#include "fast_float/fast_float.h" -#include #include #include #include @@ -19,29 +21,17 @@ #include #include #include -#include #include -template -double findmax_fastfloat64(std::vector> &s) { - double answer = 0; - double x = 0; - for (auto &st : s) { - auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x); - if (p == st.data()) { - throw std::runtime_error("bug in findmax_fastfloat"); - } - answer = answer > x ? answer : x; - } - return answer; -} +#include "fast_float/fast_float.h" -template -double findmax_fastfloat32(std::vector> &s) { - float answer = 0; - float x = 0; +template +Value findmax_fastfloat(std::vector> &s) { + Value answer = 0; + Value x = 0; for (auto &st : s) { auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x); + if (p == st.data()) { throw std::runtime_error("bug in findmax_fastfloat"); } @@ -50,9 +40,10 @@ double findmax_fastfloat32(std::vector> &s) { return answer; } +#ifdef USING_COUNTERS + event_collector collector{}; -#ifdef USING_COUNTERS template std::vector time_it_ns(std::vector> &lines, T const &function, @@ -61,7 +52,7 @@ time_it_ns(std::vector> &lines, T const &function, bool printed_bug = false; for (size_t i = 0; i < repeat; i++) { collector.start(); - double ts = function(lines); + auto const ts = function(lines); if (ts == 0 && !printed_bug) { printf("bug\n"); printed_bug = true; @@ -71,7 +62,7 @@ time_it_ns(std::vector> &lines, T const &function, return aggregate; } -void pretty_print(double volume, size_t number_of_floats, std::string name, +void pretty_print(size_t volume, size_t number_of_floats, std::string name, std::vector events) { double volumeMB = volume / (1024. * 1024.); double average_ns{0}; @@ -141,14 +132,14 @@ time_it_ns(std::vector> &lines, T const &function, bool printed_bug = false; for (size_t i = 0; i < repeat; i++) { t1 = std::chrono::high_resolution_clock::now(); - double ts = function(lines); + auto const ts = function(lines); if (ts == 0 && !printed_bug) { printf("bug\n"); printed_bug = true; } t2 = std::chrono::high_resolution_clock::now(); - double dif = - std::chrono::duration_cast(t2 - t1).count(); + double const dif = static_cast( + std::chrono::duration_cast(t2 - t1).count()); average += dif; min_value = min_value < dif ? min_value : dif; } @@ -156,8 +147,8 @@ time_it_ns(std::vector> &lines, T const &function, return std::make_pair(min_value, average); } -void pretty_print(double volume, size_t number_of_floats, std::string name, - std::pair result) { +void pretty_print(size_t volume, size_t number_of_floats, + std::string const &name, std::pair result) { double volumeMB = volume / (1024. * 1024.); printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(), volumeMB * 1000000000 / result.first, @@ -168,7 +159,7 @@ void pretty_print(double volume, size_t number_of_floats, std::string name, #endif // this is okay, all chars are ASCII -inline std::u16string widen(std::string line) { +inline std::u16string widen(std::string const &line) { std::u16string u16line; u16line.resize(line.size()); for (size_t i = 0; i < line.size(); ++i) { @@ -181,28 +172,29 @@ std::vector widen(const std::vector &lines) { std::vector u16lines; u16lines.reserve(lines.size()); for (auto const &line : lines) { - u16lines.push_back(widen(line)); + u16lines.emplace_back(widen(line)); } return u16lines; } void process(std::vector &lines, size_t volume) { - size_t repeat = 1000; + size_t const repeat = 1000; double volumeMB = volume / (1024. * 1024.); std::cout << "ASCII volume = " << volumeMB << " MB " << std::endl; pretty_print(volume, lines.size(), "fastfloat (64)", - time_it_ns(lines, findmax_fastfloat64, repeat)); + time_it_ns(lines, findmax_fastfloat, repeat)); pretty_print(volume, lines.size(), "fastfloat (32)", - time_it_ns(lines, findmax_fastfloat32, repeat)); + time_it_ns(lines, findmax_fastfloat, repeat)); std::vector lines16 = widen(lines); volume = 2 * volume; volumeMB = volume / (1024. * 1024.); std::cout << "UTF-16 volume = " << volumeMB << " MB " << std::endl; - pretty_print(volume, lines.size(), "fastfloat (64)", - time_it_ns(lines16, findmax_fastfloat64, repeat)); + pretty_print( + volume, lines.size(), "fastfloat (64)", + time_it_ns(lines16, findmax_fastfloat, repeat)); pretty_print(volume, lines.size(), "fastfloat (32)", - time_it_ns(lines16, findmax_fastfloat32, repeat)); + time_it_ns(lines16, findmax_fastfloat, repeat)); } void fileload(std::string filename) { @@ -216,17 +208,30 @@ void fileload(std::string filename) { std::cout << "#### " << std::endl; std::string line; std::vector lines; - lines.reserve(10000); // let us reserve plenty of memory. + lines.reserve(120000); // let us reserve plenty of memory. size_t volume = 0; while (getline(inputfile, line)) { - volume += line.size(); - lines.push_back(line); +#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + if (line[0] == '-') { + line.erase(0, 1); + } +#endif + volume += lines.emplace_back(line).size(); } std::cout << "# read " << lines.size() << " lines " << std::endl; process(lines, volume); } int main(int argc, char **argv) { +#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + std::cout << "# FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN is enabled" + << std::endl; +#endif +#ifdef FASTFLOAT_ONLY_ROUNDS_TO_NEAREST_SUPPORTED + std::cout << "# FASTFLOAT_ONLY_ROUNDS_TO_NEAREST_SUPPORTED is enabled" + << std::endl; +#endif +#ifdef USING_COUNTERS if (collector.has_events()) { std::cout << "# Using hardware counters" << std::endl; } else { @@ -236,10 +241,12 @@ int main(int argc, char **argv) { << std::endl; #endif } +#endif if (argc > 1) { fileload(argv[1]); return EXIT_SUCCESS; } + fileload(std::string(BENCHMARK_DATA_DIR) + "/canada.txt"); fileload(std::string(BENCHMARK_DATA_DIR) + "/mesh.txt"); return EXIT_SUCCESS; diff --git a/benchmarks/event_counter.h b/benchmarks/event_counter.h index cd59478..ee37f08 100644 --- a/benchmarks/event_counter.h +++ b/benchmarks/event_counter.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include "linux-perf-events.h" #ifdef __linux__ @@ -22,26 +22,28 @@ #endif struct event_count { + // The types of counters (so we can read the getter more easily) + enum event_counter_types { + CPU_CYCLES = 0, + INSTRUCTIONS = 1, + BRANCHES = 2, + MISSED_BRANCHES = 3, + event_counter_types_size = 4 + }; + std::chrono::duration elapsed; - std::vector event_counts; + std::array event_counts; - event_count() : elapsed(0), event_counts{0, 0, 0, 0, 0} {} + event_count() : elapsed(0), event_counts{0, 0, 0, 0} {} - event_count(const std::chrono::duration _elapsed, - const std::vector _event_counts) + event_count(const std::chrono::duration &_elapsed, + const std::array + &_event_counts) : elapsed(_elapsed), event_counts(_event_counts) {} event_count(const event_count &other) : elapsed(other.elapsed), event_counts(other.event_counts) {} - // The types of counters (so we can read the getter more easily) - enum event_counter_types { - CPU_CYCLES = 0, - INSTRUCTIONS = 1, - BRANCHES = 2, - MISSED_BRANCHES = 3 - }; - double elapsed_sec() const { return std::chrono::duration(elapsed).count(); } @@ -79,7 +81,6 @@ struct event_count { event_counts[1] + other.event_counts[1], event_counts[2] + other.event_counts[2], event_counts[3] + other.event_counts[3], - event_counts[4] + other.event_counts[4], }); } @@ -129,7 +130,7 @@ struct event_collector { LinuxEvents linux_events; event_collector() - : linux_events(std::vector{ + : linux_events(std::array{ PERF_COUNT_HW_CPU_CYCLES, PERF_COUNT_HW_INSTRUCTIONS, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, // Retired branch instructions PERF_COUNT_HW_BRANCH_MISSES}) {} @@ -142,7 +143,7 @@ struct event_collector { bool has_events() { return setup_performance_counters(); } #else - event_collector() {} + event_collector() = default; bool has_events() { return false; } #endif @@ -171,7 +172,6 @@ struct event_collector { count.event_counts[1] = diff.instructions; count.event_counts[2] = diff.branches; count.event_counts[3] = diff.missed_branches; - count.event_counts[4] = 0; #endif count.elapsed = end_clock - start_clock; return count; diff --git a/benchmarks/linux-perf-events.h b/benchmarks/linux-perf-events.h index 0a9e553..1076c6d 100644 --- a/benchmarks/linux-perf-events.h +++ b/benchmarks/linux-perf-events.h @@ -10,7 +10,7 @@ #include // for memset #include -#include +#include #include template class LinuxEvents { @@ -22,7 +22,8 @@ template class LinuxEvents { std::vector ids{}; public: - explicit LinuxEvents(std::vector config_vec) : fd(0), working(true) { + explicit LinuxEvents(std::array config_vec) + : fd(0), working(true) { memset(&attribs, 0, sizeof(attribs)); attribs.type = TYPE; attribs.size = sizeof(attribs); @@ -75,7 +76,7 @@ template class LinuxEvents { } } - inline void end(std::vector &results) { + inline void end(std::array &results) { if (fd != -1) { if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) { report_error("ioctl(PERF_EVENT_IOC_DISABLE)"); diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 97f0681..dab21f2 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -35,12 +35,14 @@ fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { return !(c > UC('9') || c < UC('0')); } +#if FASTFLOAT_HAS_BYTESWAP == 0 fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; } +#endif // Read 8 UC into a u64. Truncates UC if not char. template @@ -48,7 +50,7 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t read8_to_u64(UC const *chars) { if (cpp20_and_in_constexpr() || !std::is_same::value) { uint64_t val = 0; - for (int i = 0; i < 8; ++i) { + for (uint8_t i = 0; i != 8; ++i) { val |= uint64_t(uint8_t(*chars)) << (i * 8); ++chars; } @@ -58,7 +60,11 @@ read8_to_u64(UC const *chars) { ::memcpy(&val, chars, sizeof(uint64_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. - val = byteswap(val); + val = +#if FASTFLOAT_HAS_BYTESWAP == 1 + std:: +#endif + byteswap(val); #endif return val; } @@ -234,10 +240,15 @@ loop_parse_if_eight_digits(char const *&p, char const *const pend, enum class parse_error { no_error, - // [JSON-only] The minus sign must be followed by an integer. - missing_integer_after_sign, // A sign must be followed by an integer or dot. missing_integer_or_dot_after_sign, + // The mantissa must have at least one digit. + no_digits_in_mantissa, + // Scientific notation requires an exponential part. + missing_exponential_part, +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + // [JSON-only] The minus sign must be followed by an integer. + missing_integer_after_sign, // [JSON-only] The integer part must not have leading zeros. leading_zeros_in_integer_part, // [JSON-only] The integer part must have at least one digit. @@ -245,17 +256,17 @@ enum class parse_error { // [JSON-only] If there is a decimal point, there must be digits in the // fractional part. no_digits_in_fractional_part, - // The mantissa must have at least one digit. - no_digits_in_mantissa, - // Scientific notation requires an exponential part. - missing_exponential_part, +#endif }; template struct parsed_number_string_t { - int64_t exponent{0}; - uint64_t mantissa{0}; + // an unsigned int avoids signed overflows (which are bad) + am_mant_t mantissa{0}; + am_pow_t exponent{0}; UC const *lastmatch{nullptr}; +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN bool negative{false}; +#endif bool valid{false}; bool too_many_digits{false}; // contains the range of the significant digits @@ -269,7 +280,7 @@ using parsed_number_string = parsed_number_string_t; template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t -report_parse_error(UC const *p, parse_error error) { +report_parse_error(UC const *p, parse_error error) noexcept { parsed_number_string_t answer; answer.valid = false; answer.lastmatch = p; @@ -282,18 +293,18 @@ report_parse_error(UC const *p, parse_error error) { template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t parse_number_string(UC const *p, UC const *pend, - parse_options_t options) noexcept { - chars_format const fmt = detail::adjust_for_feature_macros(options.format); - UC const decimal_point = options.decimal_point; - + parse_options_t const options) noexcept { + // Cyclomatic complexity https://en.wikipedia.org/wiki/Cyclomatic_complexity + // Consider refactoring the 'parse_number_string' function. + // FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN fix this. parsed_number_string_t answer; - answer.valid = false; - answer.too_many_digits = false; - // assume p < pend, so dereference without checks; + FASTFLOAT_ASSUME(p < pend); // so dereference without checks; +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN answer.negative = (*p == UC('-')); // C++17 20.19.3.(7.1) explicitly forbids '+' sign here - if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && - !basic_json_fmt && *p == UC('+'))) { + if ((*p == UC('-')) || + (chars_format_t(options.format & chars_format::allow_leading_plus) && + !basic_json_fmt && *p == UC('+'))) { ++p; if (p == pend) { return report_parse_error( @@ -307,28 +318,30 @@ parse_number_string(UC const *p, UC const *pend, } else { if (!is_integer(*p) && - (*p != - decimal_point)) { // a sign must be followed by an integer or the dot + (*p != options.decimal_point)) { // a sign must be followed by an + // integer or the dot return report_parse_error( p, parse_error::missing_integer_or_dot_after_sign); } } } - UC const *const start_digits = p; +#endif - uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + UC const *const start_digits = p; while ((p != pend) && is_integer(*p)) { // a multiplication by 10 is cheaper than an arbitrary integer // multiplication - i = 10 * i + - uint64_t(*p - - UC('0')); // might overflow, we will handle the overflow later + answer.mantissa = + 10 * answer.mantissa + + UC(*p - UC('0')); // might overflow, we will handle the overflow later ++p; } UC const *const end_of_integer_part = p; - int64_t digit_count = int64_t(end_of_integer_part - start_digits); - answer.integer = span(start_digits, size_t(digit_count)); + am_digits digit_count = + static_cast(end_of_integer_part - start_digits); + answer.integer = span(start_digits, digit_count); +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { // at least 1 digit in integer part, without leading zeros if (digit_count == 0) { @@ -339,57 +352,75 @@ parse_number_string(UC const *p, UC const *pend, parse_error::leading_zeros_in_integer_part); } } +#endif - int64_t exponent = 0; - bool const has_decimal_point = (p != pend) && (*p == decimal_point); + bool const has_decimal_point = (p != pend) && (*p == options.decimal_point); if (has_decimal_point) { ++p; UC const *before = p; // can occur at most twice without overflowing, but let it occur more, since // for integers with many digits, digit parsing is the primary bottleneck. - loop_parse_if_eight_digits(p, pend, i); + loop_parse_if_eight_digits(p, pend, answer.mantissa); while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - UC('0')); + UC const digit = UC(*p - UC('0')); + answer.mantissa = + answer.mantissa * 10 + + digit; // in rare cases, this will overflow, but that's ok ++p; - i = i * 10 + digit; // in rare cases, this will overflow, but that's ok } - exponent = before - p; - answer.fraction = span(before, size_t(p - before)); - digit_count -= exponent; - } - FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { - // at least 1 digit in fractional part - if (has_decimal_point && exponent == 0) { - return report_parse_error(p, - parse_error::no_digits_in_fractional_part); + answer.exponent = static_cast(before - p); + answer.fraction = + span(before, static_cast(p - before)); + digit_count -= answer.exponent; +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in fractional part + if (has_decimal_point && answer.exponent == 0) { + return report_parse_error( + p, parse_error::no_digits_in_fractional_part); + } } - } - else if (digit_count == 0) { // we must have encountered at least one integer! +#endif + } else if (digit_count == + 0) { // we must have encountered at least one integer! return report_parse_error(p, parse_error::no_digits_in_mantissa); } - int64_t exp_number = 0; // explicit exponential part - if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && - ((UC('e') == *p) || (UC('E') == *p))) || - (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && - ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || - (UC('D') == *p)))) { + // We have now parsed the integer and the fraction part of the mantissa. + + // Now we can parse the explicit exponential part. + am_pow_t exp_number = 0; // explicit exponential part + if ((p != pend) && + (chars_format_t(options.format & chars_format::scientific) && + (UC('e') == *p) || + (UC('E') == *p)) +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + || (chars_format_t(options.format & detail::basic_fortran_fmt) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || + (UC('D') == *p))) +#endif + ) { UC const *location_of_e = p; +#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + ++p; +#else if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) { ++p; } +#endif bool neg_exp = false; - if ((p != pend) && (UC('-') == *p)) { - neg_exp = true; - ++p; - } else if ((p != pend) && - (UC('+') == - *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) - ++p; + if (p != pend) { + if (UC('-') == *p) { + neg_exp = true; + ++p; + } else if (UC('+') == *p) { + // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } } if ((p == pend) || !is_integer(*p)) { - if (!uint64_t(fmt & chars_format::fixed)) { + if (!chars_format_t(options.format & chars_format::fixed)) { // The exponential part is invalid for scientific notation, so it must // be a trailing token for fixed notation. However, fixed notation is // disabled, so report a scientific notation error. @@ -399,8 +430,9 @@ parse_number_string(UC const *p, UC const *pend, p = location_of_e; } else { while ((p != pend) && is_integer(*p)) { - uint8_t digit = uint8_t(*p - UC('0')); - if (exp_number < 0x10000000) { + if (exp_number < 0x1000) { + // check for exponent overflow if we have too many digits. + UC const digit = UC(*p - UC('0')); exp_number = 10 * exp_number + digit; } ++p; @@ -408,12 +440,12 @@ parse_number_string(UC const *p, UC const *pend, if (neg_exp) { exp_number = -exp_number; } - exponent += exp_number; + answer.exponent += exp_number; } } else { // If it scientific and not fixed, we have to bail out. - if (uint64_t(fmt & chars_format::scientific) && - !uint64_t(fmt & chars_format::fixed)) { + if (chars_format_t(options.format & chars_format::scientific) && + !chars_format_t(options.format & chars_format::fixed)) { return report_parse_error(p, parse_error::missing_exponential_part); } } @@ -431,11 +463,12 @@ parse_number_string(UC const *p, UC const *pend, // We need to be mindful of the case where we only have zeroes... // E.g., 0.000000000...000. UC const *start = start_digits; - while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + while ((start != pend) && + (*start == UC('0') || *start == options.decimal_point)) { if (*start == UC('0')) { - digit_count--; + --digit_count; } - start++; + ++start; } if (digit_count > 19) { @@ -443,44 +476,44 @@ parse_number_string(UC const *p, UC const *pend, // Let us start again, this time, avoiding overflows. // We don't need to check if is_integer, since we use the // pre-tokenized spans from above. - i = 0; + answer.mantissa = 0; p = answer.integer.ptr; UC const *int_end = p + answer.integer.len(); - uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; - while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { - i = i * 10 + uint64_t(*p - UC('0')); + am_mant_t const minimal_nineteen_digit_integer{1000000000000000000}; + while ((answer.mantissa < minimal_nineteen_digit_integer) && + (p != int_end)) { + answer.mantissa = answer.mantissa * 10 + UC(*p - UC('0')); ++p; } - if (i >= minimal_nineteen_digit_integer) { // We have a big integers - exponent = end_of_integer_part - p + exp_number; + if (answer.mantissa >= + minimal_nineteen_digit_integer) { // We have a big integers + answer.exponent = am_pow_t(end_of_integer_part - p) + exp_number; } else { // We have a value with a fractional component. p = answer.fraction.ptr; UC const *frac_end = p + answer.fraction.len(); - while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { - i = i * 10 + uint64_t(*p - UC('0')); + while ((answer.mantissa < minimal_nineteen_digit_integer) && + (p != frac_end)) { + answer.mantissa = answer.mantissa * 10 + UC(*p - UC('0')); ++p; } - exponent = answer.fraction.ptr - p + exp_number; + answer.exponent = am_pow_t(answer.fraction.ptr - p) + exp_number; } - // We have now corrected both exponent and i, to a truncated value + // We have now corrected both exponent and mantissa, to a truncated value } } - answer.exponent = exponent; - answer.mantissa = i; return answer; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t parse_int_string(UC const *p, UC const *pend, T &value, - parse_options_t options) { - chars_format const fmt = detail::adjust_for_feature_macros(options.format); - int const base = options.base; + parse_options_t const options) noexcept { from_chars_result_t answer; UC const *const first = p; +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN bool const negative = (*p == UC('-')); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) @@ -495,9 +528,11 @@ parse_int_string(UC const *p, UC const *pend, T &value, return answer; } if ((*p == UC('-')) || - (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) { + (chars_format_t(options.format & chars_format::allow_leading_plus) && + (*p == UC('+')))) { ++p; } +#endif UC const *const start_num = p; @@ -510,19 +545,19 @@ parse_int_string(UC const *p, UC const *pend, T &value, UC const *const start_digits = p; uint64_t i = 0; - if (base == 10) { + if (options.base == 10) { loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible } while (p != pend) { - uint8_t digit = ch_to_digit(*p); - if (digit >= base) { + uint8_t const digit = ch_to_digit(*p); + if (digit >= options.base) { break; } - i = uint64_t(base) * i + digit; // might overflow, check this later + i = uint64_t(options.base) * i + digit; // might overflow, check this later p++; } - size_t digit_count = size_t(p - start_digits); + uint16_t const digit_count = static_cast(p - start_digits); if (digit_count == 0) { if (has_leading_zeros) { @@ -539,26 +574,33 @@ parse_int_string(UC const *p, UC const *pend, T &value, answer.ptr = p; // check u64 overflow - size_t max_digits = max_digits_u64(base); + uint8_t const max_digits = max_digits_u64(options.base); if (digit_count > max_digits) { answer.ec = std::errc::result_out_of_range; return answer; } // this check can be eliminated for all other types, but they will all require // a max_digits(base) equivalent - if (digit_count == max_digits && i < min_safe_u64(base)) { + if (digit_count == max_digits && i < min_safe_u64(options.base)) { answer.ec = std::errc::result_out_of_range; return answer; } // check other types overflow if (!std::is_same::value) { - if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + if (i > uint64_t(std::numeric_limits::max()) +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + + uint64_t(negative) +#endif + ) { answer.ec = std::errc::result_out_of_range; return answer; } } +#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + value = T(i); +#else if (negative) { #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) @@ -578,6 +620,7 @@ parse_int_string(UC const *p, UC const *pend, T &value, } else { value = T(i); } +#endif answer.ec = std::errc(); return answer; diff --git a/include/fast_float/bigint.h b/include/fast_float/bigint.h index 74901e3..489ad11 100644 --- a/include/fast_float/bigint.h +++ b/include/fast_float/bigint.h @@ -19,11 +19,11 @@ namespace fast_float { #if defined(FASTFLOAT_64BIT) && !defined(__sparc) #define FASTFLOAT_64BIT_LIMB 1 typedef uint64_t limb; -constexpr size_t limb_bits = 64; +constexpr limb_t limb_bits = 64; #else #define FASTFLOAT_32BIT_LIMB typedef uint32_t limb; -constexpr size_t limb_bits = 32; +constexpr limb_t limb_bits = 32; #endif typedef span limb_span; @@ -32,59 +32,58 @@ typedef span limb_span; // of bits required to store the largest bigint, which is // `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or // ~3600 bits, so we round to 4000. -constexpr size_t bigint_bits = 4000; -constexpr size_t bigint_limbs = bigint_bits / limb_bits; +typedef uint16_t bigint_bits_t; +constexpr bigint_bits_t bigint_bits = 4000; +constexpr limb_t bigint_limbs = bigint_bits / limb_bits; // vector-like type that is allocated on the stack. the entire // buffer is pre-allocated, and only the length changes. -template struct stackvec { +template struct stackvec { limb data[size]; // we never need more than 150 limbs - uint16_t length{0}; + uint8_t length{0}; - stackvec() = default; + FASTFLOAT_CONSTEXPR20 stackvec() noexcept = default; stackvec(stackvec const &) = delete; stackvec &operator=(stackvec const &) = delete; stackvec(stackvec &&) = delete; stackvec &operator=(stackvec &&other) = delete; // create stack vector from existing limb span. - FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) noexcept { FASTFLOAT_ASSERT(try_extend(s)); } - FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { + FASTFLOAT_CONSTEXPR14 limb &operator[](limb_t index) noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } - FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { + FASTFLOAT_CONSTEXPR14 const limb &operator[](limb_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } // index from the end of the container - FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { + FASTFLOAT_CONSTEXPR14 const limb &rindex(limb_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); - size_t rindex = length - index - 1; + limb_t rindex = length - index - 1; return data[rindex]; } // set the length, without bounds checking. - FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { - length = uint16_t(len); - } + FASTFLOAT_CONSTEXPR14 void set_len(limb_t len) noexcept { length = len; } - constexpr size_t len() const noexcept { return length; } + constexpr limb_t len() const noexcept { return length; } constexpr bool is_empty() const noexcept { return length == 0; } - constexpr size_t capacity() const noexcept { return size; } + constexpr limb_t capacity() const noexcept { return size; } // append item to vector, without bounds checking FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { data[length] = value; - length++; + ++length; } // append item to vector, returning if item was added @@ -101,7 +100,7 @@ template struct stackvec { FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { limb *ptr = data + length; std::copy_n(s.ptr, s.len(), ptr); - set_len(len() + s.len()); + set_len(limb_t(len() + s.len())); } // try to add items to the vector, returning if items were added @@ -118,37 +117,34 @@ template struct stackvec { // if the new size is longer than the vector, assign value to each // appended item. FASTFLOAT_CONSTEXPR20 - void resize_unchecked(size_t new_len, limb value) noexcept { + void resize_unchecked(limb_t new_len, limb value) noexcept { if (new_len > len()) { - size_t count = new_len - len(); + limb_t count = new_len - len(); limb *first = data + len(); limb *last = first + count; ::std::fill(first, last, value); - set_len(new_len); - } else { - set_len(new_len); } + set_len(new_len); } // try to resize the vector, returning if the vector was resized. - FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + FASTFLOAT_CONSTEXPR20 bool try_resize(limb_t new_len, limb value) noexcept { if (new_len > capacity()) { return false; - } else { - resize_unchecked(new_len, value); - return true; } + resize_unchecked(new_len, value); + return true; } // check if any limbs are non-zero after the given index. // this needs to be done in reverse order, since the index // is relative to the most significant limbs. - FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + FASTFLOAT_CONSTEXPR14 bool nonzero(limb_t index) const noexcept { while (index < len()) { if (rindex(index) != 0) { return true; } - index++; + ++index; } return false; } @@ -156,7 +152,7 @@ template struct stackvec { // normalize the big integer, so most-significant zero limbs are removed. FASTFLOAT_CONSTEXPR14 void normalize() noexcept { while (len() > 0 && rindex(0) == 0) { - length--; + --length; } } }; @@ -258,16 +254,15 @@ scalar_mul(limb x, limb y, limb &carry) noexcept { // add scalar value to bigint starting from offset. // used in grade school multiplication -template +template inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, - size_t start) noexcept { - size_t index = start; + limb_t start) noexcept { limb carry = y; bool overflow; - while (carry != 0 && index < vec.len()) { - vec[index] = scalar_add(vec[index], carry, overflow); + while (carry != 0 && start < vec.len()) { + vec[start] = scalar_add(vec[start], carry, overflow); carry = limb(overflow); - index += 1; + ++start; } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); @@ -276,18 +271,18 @@ inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, } // add scalar value to bigint. -template +template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool small_add(stackvec &vec, limb y) noexcept { return small_add_from(vec, y, 0); } // multiply bigint by scalar value. -template +template inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, limb y) noexcept { limb carry = 0; - for (size_t index = 0; index < vec.len(); index++) { + for (limb_t index = 0; index != vec.len(); ++index) { vec[index] = scalar_mul(vec[index], y, carry); } if (carry != 0) { @@ -298,17 +293,17 @@ inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, // add bigint to bigint starting from index. // used in grade school multiplication -template +template FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, - size_t start) noexcept { + limb_t start) noexcept { // the effective x buffer is from `xstart..x.len()`, so exit early // if we can't get that current range. if (x.len() < start || y.len() > x.len() - start) { - FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + FASTFLOAT_TRY(x.try_resize(limb_t(y.len() + start), 0)); } bool carry = false; - for (size_t index = 0; index < y.len(); index++) { + for (limb_t index = 0; index < y.len(); ++index) { limb xi = x[index + start]; limb yi = y[index]; bool c1 = false; @@ -323,20 +318,20 @@ FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, // handle overflow if (carry) { - FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + FASTFLOAT_TRY(small_add_from(x, 1, limb_t(y.len() + start))); } return true; } // add bigint to bigint. -template +template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y) noexcept { return large_add_from(x, y, 0); } // grade-school multiplication algorithm -template +template FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { limb_span xs = limb_span(x.data, x.len()); stackvec z(xs); @@ -345,7 +340,7 @@ FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { if (y.len() != 0) { limb y0 = y[0]; FASTFLOAT_TRY(small_mul(x, y0)); - for (size_t index = 1; index < y.len(); index++) { + for (limb_t index = 1; index != y.len(); ++index) { limb yi = y[index]; stackvec zi; if (yi != 0) { @@ -364,7 +359,7 @@ FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { } // grade-school multiplication algorithm -template +template FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { if (y.len() == 1) { FASTFLOAT_TRY(small_mul(x, y[0])); @@ -375,7 +370,7 @@ FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { } template struct pow5_tables { - static constexpr uint32_t large_step = 135; + static constexpr uint8_t large_step = 135; static constexpr uint64_t small_power_of_5[] = { 1UL, 5UL, @@ -419,7 +414,7 @@ template struct pow5_tables { #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE -template constexpr uint32_t pow5_tables::large_step; +template constexpr uint8_t pow5_tables::large_step; template constexpr uint64_t pow5_tables::small_power_of_5[]; @@ -435,14 +430,14 @@ struct bigint : pow5_tables<> { // storage of the limbs, in little-endian order. stackvec vec; - FASTFLOAT_CONSTEXPR20 bigint() : vec() {} + FASTFLOAT_CONSTEXPR20 bigint() noexcept : vec() {} bigint(bigint const &) = delete; bigint &operator=(bigint const &) = delete; bigint(bigint &&) = delete; bigint &operator=(bigint &&other) = delete; - FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) noexcept : vec() { #ifdef FASTFLOAT_64BIT_LIMB vec.push_unchecked(value); #else @@ -493,7 +488,7 @@ struct bigint : pow5_tables<> { } else if (vec.len() < other.vec.len()) { return -1; } else { - for (size_t index = vec.len(); index > 0; index--) { + for (limb_t index = vec.len(); index != 0; --index) { limb xi = vec[index - 1]; limb yi = other.vec[index - 1]; if (xi > yi) { @@ -508,7 +503,7 @@ struct bigint : pow5_tables<> { // shift left each limb n bits, carrying over to the new limb // returns true if we were able to shift all the digits. - FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + FASTFLOAT_CONSTEXPR20 bool shl_bits(bigint_bits_t n) noexcept { // Internally, for each item, we shift left by n, and add the previous // right shifted limb-bits. // For example, we transform (for u8) shifted left 2, to: @@ -517,10 +512,10 @@ struct bigint : pow5_tables<> { FASTFLOAT_DEBUG_ASSERT(n != 0); FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); - size_t shl = n; - size_t shr = limb_bits - shl; + bigint_bits_t const shl = n; + bigint_bits_t const shr = limb_bits - shl; limb prev = 0; - for (size_t index = 0; index < vec.len(); index++) { + for (limb_t index = 0; index != vec.len(); ++index) { limb xi = vec[index]; vec[index] = (xi << shl) | (prev >> shr); prev = xi; @@ -534,30 +529,32 @@ struct bigint : pow5_tables<> { } // move the limbs left by `n` limbs. - FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_CONSTEXPR20 bool shl_limbs(bigint_bits_t n) noexcept { FASTFLOAT_DEBUG_ASSERT(n != 0); if (n + vec.len() > vec.capacity()) { + // we can't shift more than the capacity of the vector. return false; - } else if (!vec.is_empty()) { - // move limbs - limb *dst = vec.data + n; - limb const *src = vec.data; - std::copy_backward(src, src + vec.len(), dst + vec.len()); - // fill in empty limbs - limb *first = vec.data; - limb *last = first + n; - ::std::fill(first, last, 0); - vec.set_len(n + vec.len()); - return true; - } else { + } + if (vec.is_empty()) { + // nothing to do return true; } + // move limbs + limb *dst = vec.data + n; + limb const *src = vec.data; + std::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb *first = vec.data; + limb *last = first + n; + ::std::fill(first, last, 0); + vec.set_len(limb_t(n + vec.len())); + return true; } // move the limbs left by `n` bits. - FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { - size_t rem = n % limb_bits; - size_t div = n / limb_bits; + FASTFLOAT_CONSTEXPR20 bool shl(bigint_bits_t n) noexcept { + bigint_bits_t const rem = n % limb_bits; + bigint_bits_t const div = n / limb_bits; if (rem != 0) { FASTFLOAT_TRY(shl_bits(rem)); } @@ -568,24 +565,24 @@ struct bigint : pow5_tables<> { } // get the number of leading zeros in the bigint. - FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + FASTFLOAT_CONSTEXPR20 limb_t ctlz() const noexcept { if (vec.is_empty()) { + // empty vector, no bits, no zeros. return 0; - } else { + } #ifdef FASTFLOAT_64BIT_LIMB - return leading_zeroes(vec.rindex(0)); + return leading_zeroes(vec.rindex(0)); #else - // no use defining a specialized leading_zeroes for a 32-bit type. - uint64_t r0 = vec.rindex(0); - return leading_zeroes(r0 << 32); + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); #endif - } } // get the number of bits in the bigint. - FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { - int lz = ctlz(); - return int(limb_bits * vec.len()) - lz; + FASTFLOAT_CONSTEXPR20 bigint_bits_t bit_length() const noexcept { + limb_t lz = ctlz(); + return limb_bits * vec.len() - lz; } FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } @@ -593,23 +590,23 @@ struct bigint : pow5_tables<> { FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } // multiply as if by 2 raised to a power. - FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } + FASTFLOAT_CONSTEXPR20 bool pow2(am_pow_t exp) noexcept { return shl(exp); } // multiply as if by 5 raised to a power. - FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + FASTFLOAT_CONSTEXPR20 bool pow5(am_pow_t exp) noexcept { // multiply by a power of 5 - size_t large_length = sizeof(large_power_of_5) / sizeof(limb); - limb_span large = limb_span(large_power_of_5, large_length); + limb_t const large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span const large = limb_span(large_power_of_5, large_length); while (exp >= large_step) { FASTFLOAT_TRY(large_mul(vec, large)); exp -= large_step; } #ifdef FASTFLOAT_64BIT_LIMB - uint32_t small_step = 27; - limb max_native = 7450580596923828125UL; + limb_t const small_step = 27; + limb const max_native = 7450580596923828125UL; #else - uint32_t small_step = 13; - limb max_native = 1220703125U; + limb_t const small_step = 13; + limb const max_native = 1220703125U; #endif while (exp >= small_step) { FASTFLOAT_TRY(small_mul(vec, max_native)); @@ -627,7 +624,7 @@ struct bigint : pow5_tables<> { } // multiply as if by 10 raised to a power. - FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_CONSTEXPR20 bool pow10(am_pow_t exp) noexcept { FASTFLOAT_TRY(pow5(exp)); return pow2(exp); } diff --git a/include/fast_float/constexpr_feature_detect.h b/include/fast_float/constexpr_feature_detect.h index 6751afe..8d409df 100644 --- a/include/fast_float/constexpr_feature_detect.h +++ b/include/fast_float/constexpr_feature_detect.h @@ -23,8 +23,16 @@ #if defined(__cpp_lib_is_constant_evaluated) && \ __cpp_lib_is_constant_evaluated >= 201811L #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#define FASTFLOAT_CONSTEVAL consteval #else #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#define FASTFLOAT_CONSTEVAL FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_byteswap) +#define FASTFLOAT_HAS_BYTESWAP 1 +#else +#define FASTFLOAT_HAS_BYTESWAP 0 #endif #if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L @@ -50,4 +58,11 @@ #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 #endif +// For support attribute [[assume]] is declared in P1774 +#if defined(__cpp_attrubute_assume) +#define FASTFLOAT_ASSUME(expr) [[assume(expr)]] +#else +#define FASTFLOAT_ASSUME(expr) +#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 9487682..847654d 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -17,9 +17,9 @@ namespace fast_float { // most significant bits and the low part corresponding to the least significant // bits. // -template +template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 -compute_product_approximation(int64_t q, uint64_t w) { +compute_product_approximation(int64_t q, uint64_t w) noexcept { int const 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 The line value128 firstproduct = full_multiplication(w, @@ -71,13 +71,13 @@ constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { // for significant digits already multiplied by 10 ** q. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa -compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { - int hilz = int(w >> 63) ^ 1; +compute_error_scaled(int64_t q, uint64_t w, int32_t lz) noexcept { + int32_t hilz = int32_t(w >> 63) ^ 1; adjusted_mantissa answer; answer.mantissa = w << hilz; - int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); - answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + - invalid_am_bias); + int32_t bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = am_pow_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + + invalid_am_bias); return answer; } @@ -143,9 +143,9 @@ compute_float(int64_t q, uint64_t w) noexcept { answer.mantissa = product.high >> shift; - answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - - binary::minimum_exponent()); - if (answer.power2 <= 0) { // we have a subnormal? + answer.power2 = am_pow_t(detail::power(int32_t(q)) + upperbit - lz - + binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal or very small value. // Here have that answer.power2 <= 0 so -answer.power2 >= 0 if (-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you @@ -155,6 +155,7 @@ compute_float(int64_t q, uint64_t w) noexcept { // result should be zero return answer; } + // We have a subnormal number. We need to shift the mantissa to the right // next line is safe because -answer.power2 + 1 < 64 answer.mantissa >>= -answer.power2 + 1; // Thankfully, we can't have both "round-to-even" and subnormals because @@ -170,7 +171,7 @@ compute_float(int64_t q, uint64_t w) noexcept { // subnormal, but we can only know this after rounding. // So we only declare a subnormal if we are smaller than the threshold. answer.power2 = - (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) + (answer.mantissa < (am_mant_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; return answer; @@ -188,18 +189,18 @@ compute_float(int64_t q, uint64_t w) noexcept { // ... we dropped out only zeroes. But if this happened, then we can go // back!!! if ((answer.mantissa << shift) == product.high) { - answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + answer.mantissa &= ~am_mant_t(1); // flip it so that we do not round up } } answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; - if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { - answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); - answer.power2++; // undo previous addition + if (answer.mantissa >= (am_mant_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (am_mant_t(1) << binary::mantissa_explicit_bits()); + ++answer.power2; // undo previous addition } - answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + answer.mantissa &= ~(am_mant_t(1) << binary::mantissa_explicit_bits()); if (answer.power2 >= binary::infinite_power()) { // infinity answer.power2 = binary::infinite_power(); answer.mantissa = 0; diff --git a/include/fast_float/digit_comparison.h b/include/fast_float/digit_comparison.h index d7ef3d9..ba3a5bc 100644 --- a/include/fast_float/digit_comparison.h +++ b/include/fast_float/digit_comparison.h @@ -39,10 +39,10 @@ constexpr static uint64_t powers_of_ten_uint64[] = {1UL, // effect on performance: in order to have a faster algorithm, we'd need // to slow down performance for faster algorithms, and this is still fast. template -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t -scientific_exponent(parsed_number_string_t &num) noexcept { - uint64_t mantissa = num.mantissa; - int32_t exponent = int32_t(num.exponent); +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int16_t +scientific_exponent(parsed_number_string_t const &num) noexcept { + am_mant_t mantissa = num.mantissa; + am_pow_t exponent = num.exponent; while (mantissa >= 10000) { mantissa /= 10000; exponent += 4; @@ -61,18 +61,22 @@ scientific_exponent(parsed_number_string_t &num) noexcept { // this converts a native floating-point number to an extended-precision float. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa -to_extended(T value) noexcept { +to_extended(T const &value) noexcept { using equiv_uint = equiv_uint_t; constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); adjusted_mantissa am; - int32_t bias = binary_format::mantissa_explicit_bits() - - binary_format::minimum_exponent(); + am_pow_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); equiv_uint bits; #if FASTFLOAT_HAS_BIT_CAST - bits = std::bit_cast(value); + bits = +#if FASTFLOAT_HAS_BIT_CAST == 1 + std:: +#endif + bit_cast(value); #else ::memcpy(&bits, &value, sizeof(T)); #endif @@ -82,8 +86,8 @@ to_extended(T value) noexcept { am.mantissa = bits & mantissa_mask; } else { // normal - am.power2 = int32_t((bits & exponent_mask) >> - binary_format::mantissa_explicit_bits()); + am.power2 = am_pow_t((bits & exponent_mask) >> + binary_format::mantissa_explicit_bits()); am.power2 -= bias; am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; } @@ -96,7 +100,7 @@ to_extended(T value) noexcept { // halfway between b and b+u. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa -to_extended_halfway(T value) noexcept { +to_extended_halfway(T const &value) noexcept { adjusted_mantissa am = to_extended(value); am.mantissa <<= 1; am.mantissa += 1; @@ -108,14 +112,14 @@ to_extended_halfway(T value) noexcept { template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, callback cb) noexcept { - int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + am_pow_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; if (-am.power2 >= mantissa_shift) { // have a denormal float - int32_t shift = -am.power2 + 1; - cb(am, std::min(shift, 64)); + am_pow_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); // check for round-up: if rounding-nearest carried us to the hidden bit. am.power2 = (am.mantissa < - (uint64_t(1) << binary_format::mantissa_explicit_bits())) + (am_mant_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; return; @@ -126,13 +130,13 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, // check for carry if (am.mantissa >= - (uint64_t(2) << binary_format::mantissa_explicit_bits())) { - am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); - am.power2++; + (am_mant_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (am_mant_t(1) << binary_format::mantissa_explicit_bits()); + ++am.power2; } // check for infinite: we could have carried to an infinite power - am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.mantissa &= ~(am_mant_t(1) << binary_format::mantissa_explicit_bits()); if (am.power2 >= binary_format::infinite_power()) { am.power2 = binary_format::infinite_power(); am.mantissa = 0; @@ -141,11 +145,12 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void -round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, +round_nearest_tie_even(adjusted_mantissa &am, am_pow_t shift, callback cb) noexcept { - uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; - uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); - uint64_t truncated_bits = am.mantissa & mask; + am_mant_t const mask = + (shift == 64) ? UINT64_MAX : (am_mant_t(1) << shift) - 1; + am_mant_t const halfway = (shift == 0) ? 0 : am_mant_t(1) << (shift - 1); + am_mant_t truncated_bits = am.mantissa & mask; bool is_above = truncated_bits > halfway; bool is_halfway = truncated_bits == halfway; @@ -158,11 +163,11 @@ round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, am.power2 += shift; bool is_odd = (am.mantissa & 1) == 1; - am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); + am.mantissa += am_mant_t(cb(is_odd, is_halfway, is_above)); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void -round_down(adjusted_mantissa &am, int32_t shift) noexcept { +round_down(adjusted_mantissa &am, am_pow_t shift) noexcept { if (shift == 64) { am.mantissa = 0; } else { @@ -223,8 +228,8 @@ is_truncated(span s) noexcept { template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void -parse_eight_digits(UC const *&p, limb &value, size_t &counter, - size_t &count) noexcept { +parse_eight_digits(UC const *&p, limb &value, am_digits &counter, + am_digits &count) noexcept { value = value * 100000000 + parse_eight_digits_unrolled(p); p += 8; counter += 8; @@ -233,12 +238,12 @@ parse_eight_digits(UC const *&p, limb &value, size_t &counter, template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void -parse_one_digit(UC const *&p, limb &value, size_t &counter, - size_t &count) noexcept { +parse_one_digit(UC const *&p, limb &value, am_digits &counter, + am_digits &count) noexcept { value = value * 10 + limb(*p - UC('0')); - p++; - counter++; - count++; + ++p; + ++counter; + ++count; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void @@ -248,28 +253,28 @@ add_native(bigint &big, limb power, limb value) noexcept { } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void -round_up_bigint(bigint &big, size_t &count) noexcept { +round_up_bigint(bigint &big, am_digits &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); - count++; + ++count; } // parse the significant digits into a big integer -template -inline FASTFLOAT_CONSTEXPR20 void -parse_mantissa(bigint &result, parsed_number_string_t &num, - size_t max_digits, size_t &digits) noexcept { +template +inline FASTFLOAT_CONSTEXPR20 am_digits +parse_mantissa(bigint &result, const parsed_number_string_t &num) 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. - size_t counter = 0; - digits = 0; + am_digits const max_digits = binary_format::max_digits(); + am_digits counter = 0; + am_digits digits = 0; limb value = 0; #ifdef FASTFLOAT_64BIT_LIMB - size_t step = 19; + am_digits const step = 19; #else - size_t step = 9; + am_digits const step = 9; #endif // process all integer digits. @@ -280,10 +285,10 @@ parse_mantissa(bigint &result, parsed_number_string_t &num, while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { - parse_eight_digits(p, value, counter, digits); + parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { - parse_one_digit(p, value, counter, digits); + parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits @@ -295,7 +300,7 @@ parse_mantissa(bigint &result, parsed_number_string_t &num, if (truncated) { round_up_bigint(result, digits); } - return; + return digits; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; @@ -314,10 +319,10 @@ parse_mantissa(bigint &result, parsed_number_string_t &num, while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { - parse_eight_digits(p, value, counter, digits); + parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { - parse_one_digit(p, value, counter, digits); + parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits @@ -326,7 +331,7 @@ parse_mantissa(bigint &result, parsed_number_string_t &num, if (truncated) { round_up_bigint(result, digits); } - return; + return digits; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; @@ -338,20 +343,20 @@ parse_mantissa(bigint &result, parsed_number_string_t &num, if (counter != 0) { add_native(result, limb(powers_of_ten_uint64[counter]), value); } + return digits; } template -inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa -positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { - FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); - adjusted_mantissa answer; +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa positive_digit_comp( + bigint &bigmant, adjusted_mantissa am, am_pow_t const exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(exponent)); bool truncated; - answer.mantissa = bigmant.hi64(truncated); - int bias = binary_format::mantissa_explicit_bits() - - binary_format::minimum_exponent(); - answer.power2 = bigmant.bit_length() - 64 + bias; + am.mantissa = bigmant.hi64(truncated); + am_pow_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + am.power2 = bigmant.bit_length() - 64 + bias; - round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { + round(am, [truncated](adjusted_mantissa &a, am_pow_t shift) { round_nearest_tie_even( a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { @@ -360,7 +365,7 @@ positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { }); }); - return answer; + return am; } // the scaling here is quite simple: we have, for the real digits `m * 10^e`, @@ -370,38 +375,45 @@ positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { // are of the same magnitude. template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( - bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint &bigmant, adjusted_mantissa am, am_pow_t const exponent) noexcept { bigint &real_digits = bigmant; - int32_t real_exp = exponent; - - // get the value of `b`, rounded down, and get a bigint representation of b+h - adjusted_mantissa am_b = am; - // gcc7 buf: use a lambda to remove the noexcept qualifier bug with - // -Wnoexcept-type. - round(am_b, - [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); + am_pow_t const &real_exp = exponent; + T b; - to_float(false, am_b, b); + { + // get the value of `b`, rounded down, and get a bigint representation of + // b+h + adjusted_mantissa am_b = am; + // gcc7 bug: use a lambda to remove the noexcept qualifier bug with + // -Wnoexcept-type. + round(am_b, [](adjusted_mantissa &a, am_pow_t shift) { + round_down(a, shift); + }); + to_float( +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + false, +#endif + am_b, b); + } adjusted_mantissa theor = to_extended_halfway(b); bigint theor_digits(theor.mantissa); - int32_t theor_exp = theor.power2; + am_pow_t theor_exp = theor.power2; // scale real digits and theor digits to be same power. - int32_t pow2_exp = theor_exp - real_exp; - uint32_t pow5_exp = uint32_t(-real_exp); + am_pow_t pow2_exp = theor_exp - real_exp; + am_pow_t pow5_exp = -real_exp; if (pow5_exp != 0) { FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); } if (pow2_exp > 0) { - FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + FASTFLOAT_ASSERT(theor_digits.pow2(pow2_exp)); } else if (pow2_exp < 0) { - FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + FASTFLOAT_ASSERT(real_digits.pow2(-pow2_exp)); } // compare digits, and use it to director rounding int ord = real_digits.compare(theor_digits); - adjusted_mantissa answer = am; - round(answer, [ord](adjusted_mantissa &a, int32_t shift) { + round(am, [ord](adjusted_mantissa &a, am_pow_t shift) { round_nearest_tie_even( a, shift, [ord](bool is_odd, bool _, bool __) -> bool { (void)_; // not needed, since we've done our comparison @@ -416,7 +428,7 @@ inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( }); }); - return answer; + return am; } // parse the significant digits as a big integer to unambiguously round the @@ -433,20 +445,19 @@ inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( // the actual digits. we then compare the big integer representations // of both, and use that to direct rounding. template -inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa -digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa digit_comp( + parsed_number_string_t const &num, adjusted_mantissa am) noexcept { // remove the invalid exponent bias am.power2 -= invalid_am_bias; - int32_t sci_exp = scientific_exponent(num); - size_t max_digits = binary_format::max_digits(); - size_t digits = 0; bigint bigmant; - parse_mantissa(bigmant, num, max_digits, digits); + am_pow_t const sci_exp = scientific_exponent(num); + + am_digits const digits = parse_mantissa(bigmant, num); // can't underflow, since digits is at most max_digits. - int32_t exponent = sci_exp + 1 - int32_t(digits); + am_pow_t const exponent = sci_exp + 1 - digits; if (exponent >= 0) { - return positive_digit_comp(bigmant, exponent); + return positive_digit_comp(bigmant, am, exponent); } else { return negative_digit_comp(bigmant, am, exponent); } diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index af65c96..4c05c2d 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -34,7 +34,7 @@ template ::value)> FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, - chars_format fmt = chars_format::general) noexcept; + chars_format const fmt = chars_format::general) noexcept; /** * Like from_chars, but accepts an `options` argument to govern number parsing. @@ -43,7 +43,7 @@ from_chars(UC const *first, UC const *last, T &value, template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept; + parse_options_t const options) noexcept; /** * from_chars for integer types. @@ -51,7 +51,8 @@ from_chars_advanced(UC const *first, UC const *last, T &value, template ::value)> FASTFLOAT_CONSTEXPR20 from_chars_result_t -from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; +from_chars(UC const *first, UC const *last, T &value, + int const base = 10) noexcept; } // namespace fast_float diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 8fd0560..90eecc2 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -33,26 +33,38 @@ namespace fast_float { -enum class chars_format : uint64_t; +// The number of digits in the mantissa. +typedef uint16_t am_digits; +// The number of bits in the limb. +typedef uint8_t limb_t; + +typedef uint8_t chars_format_t; + +enum class chars_format : chars_format_t; + +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN namespace detail { -constexpr chars_format basic_json_fmt = chars_format(1 << 5); -constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); +constexpr chars_format basic_json_fmt = chars_format(1 << 4); +constexpr chars_format basic_fortran_fmt = chars_format(1 << 5); } // namespace detail +#endif -enum class chars_format : uint64_t { +enum class chars_format : chars_format_t { scientific = 1 << 0, - fixed = 1 << 2, - hex = 1 << 3, - no_infnan = 1 << 4, + fixed = 1 << 1, + general = fixed | scientific, + hex = 1 << 2, +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + no_infnan = 1 << 3, // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 - json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan, + json = uint64_t(detail::basic_json_fmt) | general | no_infnan, // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. - json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific, - fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific, - general = fixed | scientific, - allow_leading_plus = 1 << 7, - skip_white_space = 1 << 8, + json_or_infnan = chars_format_t(detail::basic_json_fmt) | general, + fortran = chars_format_t(detail::basic_fortran_fmt) | general, + allow_leading_plus = 1 << 6, + skip_white_space = 1 << 7, +#endif }; template struct from_chars_result_t { @@ -63,16 +75,22 @@ template struct from_chars_result_t { using from_chars_result = from_chars_result_t; template struct parse_options_t { - constexpr explicit parse_options_t(chars_format fmt = chars_format::general, - UC dot = UC('.'), int b = 10) - : format(fmt), decimal_point(dot), base(b) {} + FASTFLOAT_CONSTEXPR20 explicit parse_options_t( + chars_format const fmt = chars_format::general, UC const dot = UC('.'), + chars_format_t const b = 10) noexcept + : format(fmt), decimal_point(dot), base(b) { +#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + // static_assert(b >= 2 && b <= 36); +#endif + } /** Which number formats are accepted */ - chars_format format; + chars_format const format; /** The character used as decimal point */ - UC decimal_point; + UC const decimal_point; /** The base used for integers */ - int base; + uint8_t const base; /* only allowed from 2 to 36 */ + FASTFLOAT_ASSUME(base >= 2 && base <= 36); }; using parse_options = parse_options_t; @@ -192,12 +210,18 @@ using parse_options = parse_options_t; #ifndef FASTFLOAT_ASSERT #define FASTFLOAT_ASSERT(x) \ - { ((void)(x)); } + { \ + ((void)(x)); \ + FASTFLOAT_ASSUME(x); \ + } #endif #ifndef FASTFLOAT_DEBUG_ASSERT #define FASTFLOAT_DEBUG_ASSERT(x) \ - { ((void)(x)); } + { \ + ((void)(x)); \ + FASTFLOAT_ASSUME(x); \ + } #endif // rust style `try!()` macro, or `?` operator @@ -212,7 +236,8 @@ using parse_options = parse_options_t; namespace fast_float { -fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +cpp20_and_in_constexpr() noexcept { #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED return std::is_constant_evaluated(); #else @@ -261,12 +286,13 @@ struct is_supported_char_type > { }; +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN // Compares two ASCII strings in a case insensitive manner. template inline FASTFLOAT_CONSTEXPR14 bool fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, - size_t length) { - for (size_t i = 0; i < length; ++i) { + uint8_t const length) noexcept { + for (uint8_t i = 0; i != length; ++i) { UC const actual = actual_mixedcase[i]; if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { return false; @@ -274,6 +300,7 @@ fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, } return true; } +#endif #ifndef FLT_EVAL_METHOD #error "FLT_EVAL_METHOD should be defined, please include cfloat." @@ -282,15 +309,16 @@ fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, // a pointer and a length to a contiguous block of memory template struct span { T const *ptr; - size_t length; + am_digits length; - constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} + constexpr span(T const *_ptr, am_digits _length) noexcept + : ptr(_ptr), length(_length) {} - constexpr span() : ptr(nullptr), length(0) {} + constexpr span() noexcept : ptr(nullptr), length(0) {} - constexpr size_t len() const noexcept { return length; } + constexpr am_digits len() const noexcept { return length; } - FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { + FASTFLOAT_CONSTEXPR14 const T &operator[](am_digits index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return ptr[index]; } @@ -300,14 +328,15 @@ struct value128 { uint64_t low; uint64_t high; - constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + constexpr value128(uint64_t _low, uint64_t _high) noexcept + : low(_low), high(_high) {} - constexpr value128() : low(0), high(0) {} + constexpr value128() noexcept : low(0), high(0) {} }; /* Helper C++14 constexpr generic implementation of leading_zeroes */ -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int -leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 limb_t +leading_zeroes_generic(uint64_t input_num, uint64_t last_bit = 0) noexcept { if (input_num & uint64_t(0xffffffff00000000)) { input_num >>= 32; last_bit |= 32; @@ -331,13 +360,14 @@ leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ last_bit |= 1; } - return 63 - last_bit; + return 63 - (limb_t)last_bit; } /* result might be undefined when input_num is zero */ -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int -leading_zeroes(uint64_t input_num) { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb_t +leading_zeroes(uint64_t input_num) noexcept { assert(input_num > 0); + FASTFLOAT_ASSUME(input_num > 0); if (cpp20_and_in_constexpr()) { return leading_zeroes_generic(input_num); } @@ -347,22 +377,23 @@ leading_zeroes(uint64_t input_num) { // Search the mask data from most significant bit (MSB) // to least significant bit (LSB) for a set bit (1). _BitScanReverse64(&leading_zero, input_num); - return (int)(63 - leading_zero); + return (limb_t)(63 - leading_zero); #else - return leading_zeroes_generic(input_num); + return (limb_t)leading_zeroes_generic(input_num); #endif #else - return __builtin_clzll(input_num); + return (limb_t)__builtin_clzll(input_num); #endif } // slow emulation routine for 32-bit -fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, + uint32_t y) noexcept { return x * (uint64_t)y; } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t -umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { +umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) noexcept { uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); @@ -377,9 +408,8 @@ umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { // slow emulation routine for 32-bit #if !defined(__MINGW64__) -fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, - uint64_t cd, - uint64_t *hi) { +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +_umul128(uint64_t ab, uint64_t cd, uint64_t *hi) noexcept { return umul128_generic(ab, cd, hi); } #endif // !__MINGW64__ @@ -388,7 +418,7 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, // compute 64-bit a*b fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 -full_multiplication(uint64_t a, uint64_t b) { +full_multiplication(uint64_t a, uint64_t b) noexcept { if (cpp20_and_in_constexpr()) { value128 answer; answer.low = umul128_generic(a, b, &answer.high); @@ -413,47 +443,56 @@ full_multiplication(uint64_t a, uint64_t b) { return answer; } +// Value of the mantissa. +typedef uint64_t am_mant_t; +// Size of bits in the mantissa. +typedef uint8_t am_bits_t; + +// Power bias is signed for handling a denormal float +// or an invalid mantissa. +typedef int16_t am_pow_t; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static am_pow_t invalid_am_bias = -0x8000; + struct adjusted_mantissa { - uint64_t mantissa{0}; - int32_t power2{0}; // a negative value indicates an invalid result - adjusted_mantissa() = default; + am_mant_t mantissa; + am_pow_t power2; + adjusted_mantissa() noexcept = default; - constexpr bool operator==(adjusted_mantissa const &o) const { + constexpr bool operator==(adjusted_mantissa const &o) const noexcept { return mantissa == o.mantissa && power2 == o.power2; } - constexpr bool operator!=(adjusted_mantissa const &o) const { + constexpr bool operator!=(adjusted_mantissa const &o) const noexcept { return mantissa != o.mantissa || power2 != o.power2; } }; -// Bias so we can get the real exponent with an invalid adjusted_mantissa. -constexpr static int32_t invalid_am_bias = -0x8000; - // used for binary_format_lookup_tables::max_mantissa -constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; +constexpr am_mant_t constant_55555 = 5 * 5 * 5 * 5 * 5; template struct binary_format_lookup_tables; template struct binary_format : binary_format_lookup_tables { using equiv_uint = equiv_uint_t; - static constexpr int mantissa_explicit_bits(); - static constexpr int minimum_exponent(); - static constexpr int infinite_power(); - static constexpr int sign_index(); - static constexpr int + static constexpr am_bits_t mantissa_explicit_bits(); + static constexpr am_pow_t minimum_exponent(); + static constexpr am_pow_t infinite_power(); + static constexpr am_bits_t sign_index(); + static constexpr am_pow_t min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST - static constexpr int max_exponent_fast_path(); - static constexpr int max_exponent_round_to_even(); - static constexpr int min_exponent_round_to_even(); - static constexpr uint64_t max_mantissa_fast_path(int64_t power); - static constexpr uint64_t + static constexpr am_pow_t max_exponent_fast_path(); + static constexpr am_pow_t max_exponent_round_to_even(); + static constexpr am_pow_t min_exponent_round_to_even(); + static constexpr equiv_uint max_mantissa_fast_path(int64_t power); + static constexpr equiv_uint max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST - static constexpr int largest_power_of_ten(); - static constexpr int smallest_power_of_ten(); + static constexpr am_pow_t largest_power_of_ten(); + static constexpr am_pow_t smallest_power_of_ten(); static constexpr T exact_power_of_ten(int64_t power); - static constexpr size_t max_digits(); + static constexpr am_digits max_digits(); static constexpr equiv_uint exponent_mask(); static constexpr equiv_uint mantissa_mask(); static constexpr equiv_uint hidden_bit_mask(); @@ -517,7 +556,7 @@ template struct binary_format_lookup_tables { // Largest integer value v so that (5**index * v) <= 1<<24. // 0x1000000 == 1<<24 - static constexpr uint64_t max_mantissa[] = { + static constexpr uint32_t max_mantissa[] = { 0x1000000, 0x1000000 / 5, 0x1000000 / (5 * 5), @@ -543,7 +582,7 @@ constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; #endif template <> -inline constexpr int binary_format::min_exponent_fast_path() { +inline constexpr am_pow_t binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else @@ -552,7 +591,7 @@ inline constexpr int binary_format::min_exponent_fast_path() { } template <> -inline constexpr int binary_format::min_exponent_fast_path() { +inline constexpr am_pow_t binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else @@ -561,77 +600,78 @@ inline constexpr int binary_format::min_exponent_fast_path() { } template <> -inline constexpr int binary_format::mantissa_explicit_bits() { +inline constexpr am_bits_t binary_format::mantissa_explicit_bits() { return 52; } template <> -inline constexpr int binary_format::mantissa_explicit_bits() { +inline constexpr am_bits_t binary_format::mantissa_explicit_bits() { return 23; } template <> -inline constexpr int binary_format::max_exponent_round_to_even() { +inline constexpr am_pow_t binary_format::max_exponent_round_to_even() { return 23; } template <> -inline constexpr int binary_format::max_exponent_round_to_even() { +inline constexpr am_pow_t binary_format::max_exponent_round_to_even() { return 10; } template <> -inline constexpr int binary_format::min_exponent_round_to_even() { +inline constexpr am_pow_t binary_format::min_exponent_round_to_even() { return -4; } template <> -inline constexpr int binary_format::min_exponent_round_to_even() { +inline constexpr am_pow_t binary_format::min_exponent_round_to_even() { return -17; } -template <> inline constexpr int binary_format::minimum_exponent() { +template <> +inline constexpr am_pow_t binary_format::minimum_exponent() { return -1023; } -template <> inline constexpr int binary_format::minimum_exponent() { +template <> inline constexpr am_pow_t binary_format::minimum_exponent() { return -127; } -template <> inline constexpr int binary_format::infinite_power() { +template <> inline constexpr am_pow_t binary_format::infinite_power() { return 0x7FF; } -template <> inline constexpr int binary_format::infinite_power() { +template <> inline constexpr am_pow_t binary_format::infinite_power() { return 0xFF; } -template <> inline constexpr int binary_format::sign_index() { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + +template <> inline constexpr am_bits_t binary_format::sign_index() { return 63; } -template <> inline constexpr int binary_format::sign_index() { +template <> inline constexpr am_bits_t binary_format::sign_index() { return 31; } +#endif + template <> -inline constexpr int binary_format::max_exponent_fast_path() { +inline constexpr am_pow_t binary_format::max_exponent_fast_path() { return 22; } template <> -inline constexpr int binary_format::max_exponent_fast_path() { +inline constexpr am_pow_t binary_format::max_exponent_fast_path() { return 10; } -template <> -inline constexpr uint64_t binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} - -template <> -inline constexpr uint64_t binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); +template +inline constexpr binary_format::equiv_uint +binary_format::max_mantissa_fast_path() { + return binary_format::equiv_uint(2) << mantissa_explicit_bits(); } // credit: Jakub Jelínek @@ -642,7 +682,7 @@ template struct binary_format_lookup_tables { // Largest integer value v so that (5**index * v) <= 1<<11. // 0x800 == 1<<11 - static constexpr uint64_t max_mantissa[] = {0x800, + static constexpr uint16_t max_mantissa[] = {0x800, 0x800 / 5, 0x800 / (5 * 5), 0x800 / (5 * 5 * 5), @@ -657,7 +697,7 @@ constexpr std::float16_t binary_format_lookup_tables::powers_of_ten[]; template -constexpr uint64_t +constexpr uint16_t binary_format_lookup_tables::max_mantissa[]; #endif @@ -688,21 +728,17 @@ binary_format::hidden_bit_mask() { } template <> -inline constexpr int binary_format::max_exponent_fast_path() { +inline constexpr int8_t +binary_format::max_exponent_fast_path() { return 4; } template <> -inline constexpr int binary_format::mantissa_explicit_bits() { +inline constexpr uint8_t +binary_format::mantissa_explicit_bits() { return 10; } -template <> -inline constexpr uint64_t -binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} - template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { @@ -714,48 +750,56 @@ binary_format::max_mantissa_fast_path(int64_t power) { } template <> -inline constexpr int binary_format::min_exponent_fast_path() { +inline constexpr int8_t +binary_format::min_exponent_fast_path() { return 0; } template <> -inline constexpr int +inline constexpr int16_t binary_format::max_exponent_round_to_even() { return 5; } template <> -inline constexpr int +inline constexpr int16_t binary_format::min_exponent_round_to_even() { return -22; } template <> -inline constexpr int binary_format::minimum_exponent() { +inline constexpr am_exp_t binary_format::minimum_exponent() { return -15; } template <> -inline constexpr int binary_format::infinite_power() { +inline constexpr am_exp_t binary_format::infinite_power() { return 0x1F; } -template <> inline constexpr int binary_format::sign_index() { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + +template <> +inline constexpr am_bits_t binary_format::sign_index() { return 15; } +#endif + template <> -inline constexpr int binary_format::largest_power_of_ten() { +inline constexpr am_exp_t +binary_format::largest_power_of_ten() { return 4; } template <> -inline constexpr int binary_format::smallest_power_of_ten() { +inline constexpr am_exp_t +binary_format::smallest_power_of_ten() { return -27; } template <> -inline constexpr size_t binary_format::max_digits() { +inline constexpr am_digits binary_format::max_digits() { return 22; } #endif // __STDCPP_FLOAT16_T__ @@ -793,7 +837,8 @@ binary_format::exact_power_of_ten(int64_t power) { } template <> -inline constexpr int binary_format::max_exponent_fast_path() { +inline constexpr int8_t +binary_format::max_exponent_fast_path() { return 3; } @@ -816,16 +861,11 @@ binary_format::hidden_bit_mask() { } template <> -inline constexpr int binary_format::mantissa_explicit_bits() { +inline constexpr uint8_t +binary_format::mantissa_explicit_bits() { return 7; } -template <> -inline constexpr uint64_t -binary_format::max_mantissa_fast_path() { - return uint64_t(2) << mantissa_explicit_bits(); -} - template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { @@ -837,48 +877,56 @@ binary_format::max_mantissa_fast_path(int64_t power) { } template <> -inline constexpr int binary_format::min_exponent_fast_path() { +inline constexpr int8_t +binary_format::min_exponent_fast_path() { return 0; } template <> -inline constexpr int +inline constexpr am_exp_t binary_format::max_exponent_round_to_even() { return 3; } template <> -inline constexpr int +inline constexpr am_exp_t binary_format::min_exponent_round_to_even() { return -24; } template <> -inline constexpr int binary_format::minimum_exponent() { +inline constexpr am_exp_t binary_format::minimum_exponent() { return -127; } template <> -inline constexpr int binary_format::infinite_power() { +inline constexpr am_exp_t binary_format::infinite_power() { return 0xFF; } -template <> inline constexpr int binary_format::sign_index() { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + +template <> +inline constexpr uint8_t binary_format::sign_index() { return 15; } +#endif + template <> -inline constexpr int binary_format::largest_power_of_ten() { +inline constexpr am_exp_t +binary_format::largest_power_of_ten() { return 38; } template <> -inline constexpr int binary_format::smallest_power_of_ten() { +inline constexpr am_exp_t +binary_format::smallest_power_of_ten() { return -60; } template <> -inline constexpr size_t binary_format::max_digits() { +inline constexpr uint16_t binary_format::max_digits() { return 98; } #endif // __STDCPP_BFLOAT16_T__ @@ -887,17 +935,17 @@ template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that - // power >= 0 && power <= 22 + FASTFLOAT_ASSUME(power >= 0 && power <= 22); // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> -inline constexpr uint64_t +inline constexpr uint32_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that - // power >= 0 && power <= 10 + FASTFLOAT_ASSUME(power >= 0 && power <= 10); // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; @@ -906,38 +954,47 @@ binary_format::max_mantissa_fast_path(int64_t power) { template <> inline constexpr double binary_format::exact_power_of_ten(int64_t power) { + // caller is responsible to ensure that + FASTFLOAT_ASSUME(power >= 0 && power <= 22); + // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // caller is responsible to ensure that + FASTFLOAT_ASSUME(power >= 0 && power <= 10); + // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } -template <> inline constexpr int binary_format::largest_power_of_ten() { +template <> +inline constexpr am_pow_t binary_format::largest_power_of_ten() { return 308; } -template <> inline constexpr int binary_format::largest_power_of_ten() { +template <> +inline constexpr am_pow_t binary_format::largest_power_of_ten() { return 38; } template <> -inline constexpr int binary_format::smallest_power_of_ten() { +inline constexpr am_pow_t binary_format::smallest_power_of_ten() { return -342; } -template <> inline constexpr int binary_format::smallest_power_of_ten() { +template <> +inline constexpr am_pow_t binary_format::smallest_power_of_ten() { return -64; } -template <> inline constexpr size_t binary_format::max_digits() { +template <> inline constexpr am_digits binary_format::max_digits() { return 769; } -template <> inline constexpr size_t binary_format::max_digits() { +template <> inline constexpr am_digits binary_format::max_digits() { return 114; } @@ -978,14 +1035,19 @@ binary_format::hidden_bit_mask() { } template -fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void -to_float(bool negative, adjusted_mantissa am, T &value) { +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void to_float( +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + bool const negative, +#endif + adjusted_mantissa const &am, T &value) noexcept { using equiv_uint = equiv_uint_t; equiv_uint word = equiv_uint(am.mantissa); word = equiv_uint(word | equiv_uint(am.power2) << binary_format::mantissa_explicit_bits()); +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN word = equiv_uint(word | equiv_uint(negative) << binary_format::sign_index()); +#endif #if FASTFLOAT_HAS_BIT_CAST value = std::bit_cast(word); #else @@ -993,8 +1055,10 @@ to_float(bool negative, adjusted_mantissa am, T &value) { #endif } +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + template struct space_lut { - static constexpr bool value[] = { + static constexpr uint8_t value[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -1010,7 +1074,7 @@ template struct space_lut { #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE -template constexpr bool space_lut::value[]; +template constexpr uint8_t space_lut::value[]; #endif @@ -1018,6 +1082,8 @@ template constexpr bool is_space(UC c) { return c < 256 && space_lut<>::value[uint8_t(c)]; } +#endif + template static constexpr uint64_t int_cmp_zeros() { static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), "Unsupported character size"); @@ -1032,6 +1098,8 @@ template static constexpr int int_cmp_len() { return sizeof(uint64_t) / sizeof(UC); } +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + template constexpr UC const *str_const_nan(); template <> constexpr char const *str_const_nan() { return "nan"; } @@ -1074,6 +1142,8 @@ template <> constexpr char8_t const *str_const_inf() { } #endif +#endif + template struct int_luts { static constexpr uint8_t chdigit[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, @@ -1095,7 +1165,7 @@ template struct int_luts { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; - static constexpr size_t maxdigits_u64[] = { + static constexpr uint8_t maxdigits_u64[] = { 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; @@ -1118,24 +1188,25 @@ template struct int_luts { template constexpr uint8_t int_luts::chdigit[]; -template constexpr size_t int_luts::maxdigits_u64[]; +template constexpr uint8_t int_luts::maxdigits_u64[]; template constexpr uint64_t int_luts::min_safe_u64[]; #endif template -fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { +fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) noexcept { return int_luts<>::chdigit[static_cast(c)]; } -fastfloat_really_inline constexpr size_t max_digits_u64(int base) { +fastfloat_really_inline constexpr uint8_t +max_digits_u64(uint8_t base) noexcept { return int_luts<>::maxdigits_u64[base - 2]; } // If a u64 is exactly max_digits_u64() in length, this is // the value below which it has definitely overflowed. -fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { +fastfloat_really_inline constexpr uint64_t min_safe_u64(uint8_t base) noexcept { return int_luts<>::min_safe_u64[base - 2]; } @@ -1221,20 +1292,6 @@ operator^=(chars_format &lhs, chars_format rhs) noexcept { return lhs = (lhs ^ rhs); } -namespace detail { -// adjust for deprecated feature macros -constexpr chars_format adjust_for_feature_macros(chars_format fmt) { - return fmt -#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS - | chars_format::allow_leading_plus -#endif -#ifdef FASTFLOAT_SKIP_WHITE_SPACE - | chars_format::skip_white_space -#endif - ; -} -} // namespace detail - } // namespace fast_float #endif diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index e74c478..3d4e3d8 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -14,6 +14,7 @@ namespace fast_float { namespace detail { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN /** * Special case +inf, -inf, nan, infinity, -infinity. * The case comparisons could be made much faster given that we know that the @@ -22,18 +23,21 @@ namespace detail { template from_chars_result_t FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, - T &value, chars_format fmt) noexcept { + T &value, + const chars_format fmt) noexcept { from_chars_result_t answer{}; answer.ptr = first; - answer.ec = std::errc(); // be optimistic - // assume first < last, so dereference without checks; + answer.ec = std::errc(); // be optimistic + FASTFLOAT_ASSUME(first < last); // so dereference without checks + bool const minusSign = (*first == UC('-')); // C++17 20.19.3.(7.1) explicitly forbids '+' sign here if ((*first == UC('-')) || - (uint64_t(fmt & chars_format::allow_leading_plus) && + (chars_format_t(fmt & chars_format::allow_leading_plus) && (*first == UC('+')))) { ++first; } + if (last - first >= 3) { if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { answer.ptr = (first += 3); @@ -69,7 +73,9 @@ from_chars_result_t answer.ec = std::errc::invalid_argument; return answer; } +#endif +#ifndef FASTFLOAT_ONLY_ROUNDS_TO_NEAREST_SUPPORTED /** * Returns true if the floating-pointing rounding mode is to 'nearest'. * It is the default on most system. This function is meant to be inexpensive. @@ -134,6 +140,7 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept { #pragma GCC diagnostic pop #endif } +#endif } // namespace detail @@ -141,7 +148,7 @@ template struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { return from_chars_advanced(first, last, value, options); } }; @@ -151,7 +158,7 @@ template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float32_t &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { // if std::float32_t is defined, and we are in C++23 mode; macro set for // float32; set value to float due to equivalence between float and // float32_t @@ -168,7 +175,7 @@ template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float64_t &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { // if std::float64_t is defined, and we are in C++23 mode; macro set for // float64; set value as double due to equivalence between double and // float64_t @@ -183,7 +190,7 @@ template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, - chars_format fmt /*= chars_format::general*/) noexcept { + chars_format const fmt /*= chars_format::general*/) noexcept { return from_chars_caller::call(first, last, value, parse_options_t(fmt)); } @@ -195,7 +202,7 @@ from_chars(UC const *first, UC const *last, T &value, */ template FASTFLOAT_CONSTEXPR20 from_chars_result_t -from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { +from_chars_advanced(parsed_number_string_t const &pns, T &value) noexcept { static_assert(is_supported_float_type::value, "only some floating-point types are supported"); @@ -221,7 +228,9 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { // We could check it first (before the previous branch), but // there might be performance advantages at having the check // be last. +#ifndef FASTFLOAT_ONLY_ROUNDS_TO_NEAREST_SUPPORTED if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { +#endif // We have that fegetround() == FE_TONEAREST. // Next is Clinger's fast path. if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { @@ -231,11 +240,14 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { } else { value = value * binary_format::exact_power_of_ten(pns.exponent); } +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN if (pns.negative) { value = -value; } +#endif return answer; } +#ifndef FASTFLOAT_ONLY_ROUNDS_TO_NEAREST_SUPPORTED } else { // We do not have that fegetround() == FE_TONEAREST. // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's @@ -246,18 +258,25 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { #if defined(__clang__) || defined(FASTFLOAT_32BIT) // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD if (pns.mantissa == 0) { - value = pns.negative ? T(-0.) : T(0.); + value = +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + pns.negative ? T(-0.) : +#endif + T(0.); return answer; } #endif value = T(pns.mantissa) * binary_format::exact_power_of_ten(pns.exponent); +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN if (pns.negative) { value = -value; } +#endif return answer; } } +#endif } adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); @@ -272,7 +291,11 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { if (am.power2 < 0) { am = digit_comp(pns, am); } - to_float(pns.negative, am, value); + to_float( +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + pns.negative, +#endif + am, value); // Test for over/underflow. if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format::infinite_power()) { @@ -284,19 +307,18 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_float_advanced(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { static_assert(is_supported_float_type::value, "only some floating-point types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); - chars_format const fmt = detail::adjust_for_feature_macros(options.format); - from_chars_result_t answer; - if (uint64_t(fmt & chars_format::skip_white_space)) { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + if (chars_format_t(options.format & chars_format::skip_white_space)) { while ((first != last) && fast_float::is_space(*first)) { - first++; + ++first; } } if (first == last) { @@ -304,18 +326,29 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value, answer.ptr = first; return answer; } - parsed_number_string_t pns = - uint64_t(fmt & detail::basic_json_fmt) +#else + // We are in parser code with external loop that checks bounds. + FASTFLOAT_ASSUME(first < last); +#endif + parsed_number_string_t const pns = +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + chars_format_t(options.format & detail::basic_json_fmt) ? parse_number_string(first, last, options) - : parse_number_string(first, last, options); + : +#endif + parse_number_string(first, last, options); if (!pns.valid) { - if (uint64_t(fmt & chars_format::no_infnan)) { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + if (chars_format_t(options.format & chars_format::no_infnan)) { +#endif answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN } else { - return detail::parse_infnan(first, last, value, fmt); + return detail::parse_infnan(first, last, value, options.format); } +#endif } // call overload that takes parsed_number_string_t directly. @@ -324,42 +357,44 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value, template FASTFLOAT_CONSTEXPR20 from_chars_result_t -from_chars(UC const *first, UC const *last, T &value, int base) noexcept { +from_chars(UC const *first, UC const *last, T &value, int const base) noexcept { static_assert(is_supported_integer_type::value, "only integer types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); - parse_options_t options; - options.base = base; + parse_options_t const options(chars_format::general, UC('.'), base); return from_chars_advanced(first, last, value, options); } template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_int_advanced(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { static_assert(is_supported_integer_type::value, "only integer types are supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); - chars_format const fmt = detail::adjust_for_feature_macros(options.format); - int const base = options.base; - - from_chars_result_t answer; - if (uint64_t(fmt & chars_format::skip_white_space)) { +#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN + if (chars_format_t(options.format & chars_format::skip_white_space)) { while ((first != last) && fast_float::is_space(*first)) { - first++; + ++first; } } - if (first == last || base < 2 || base > 36) { + if (first == last || options.base < 2 || options.base > 36) { + from_chars_result_t answer; answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } +#else + // We are in parser code with external loop that checks bounds. + FASTFLOAT_ASSUME(first < last); + // base is already checked in the parse_options_t constructor. +#endif return parse_int_string(first, last, value, options); } @@ -372,7 +407,7 @@ template <> struct from_chars_advanced_caller<1> { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { return from_chars_float_advanced(first, last, value, options); } }; @@ -381,7 +416,7 @@ template <> struct from_chars_advanced_caller<2> { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { return from_chars_int_advanced(first, last, value, options); } }; @@ -389,7 +424,7 @@ template <> struct from_chars_advanced_caller<2> { template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + parse_options_t const options) noexcept { return from_chars_advanced_caller< size_t(is_supported_float_type::value) + 2 * size_t(is_supported_integer_type::value)>::call(first, last, value, diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 552d6f1..9b61bdf 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -644,19 +644,20 @@ TEST_CASE("check_behavior") { TEST_CASE("decimal_point_parsing") { double result; - fast_float::parse_options options{}; { std::string const input = "1,25"; auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options{}); CHECK_MESSAGE(answer.ec == std::errc(), "expected parse success"); CHECK_MESSAGE(answer.ptr == input.data() + 1, "Parsing should have stopped at comma"); CHECK_EQ(result, 1.0); - options.decimal_point = ','; answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options( + {fast_float::chars_format::general, ',', 10})); CHECK_MESSAGE(answer.ec == std::errc(), "expected parse success"); CHECK_MESSAGE(answer.ptr == input.data() + input.size(), "Parsing should have stopped at end"); @@ -665,15 +666,17 @@ TEST_CASE("decimal_point_parsing") { { std::string const input = "1.25"; auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options( + {fast_float::chars_format::general, ',', 10})); CHECK_MESSAGE(answer.ec == std::errc(), "expected parse success"); CHECK_MESSAGE(answer.ptr == input.data() + 1, "Parsing should have stopped at dot"); CHECK_EQ(result, 1.0); - options.decimal_point = '.'; answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options{}); CHECK_MESSAGE(answer.ec == std::errc(), "expected parse success"); CHECK_MESSAGE(answer.ptr == input.data() + input.size(), "Parsing should have stopped at end"); @@ -1322,9 +1325,8 @@ TEST_CASE("double.general") { TEST_CASE("double.decimal_point") { constexpr auto options = [] { - fast_float::parse_options ret{}; - ret.decimal_point = ','; - return ret; + return fast_float::parse_options( + {fast_float::chars_format::general, ',', 10}); }(); // infinities @@ -1641,9 +1643,8 @@ TEST_CASE("float.general") { TEST_CASE("float.decimal_point") { constexpr auto options = [] { - fast_float::parse_options ret{}; - ret.decimal_point = ','; - return ret; + return fast_float::parse_options( + {fast_float::chars_format::general, ',', 10}); }(); // infinity diff --git a/tests/example_comma_test.cpp b/tests/example_comma_test.cpp index 79a8e1d..b77ad57 100644 --- a/tests/example_comma_test.cpp +++ b/tests/example_comma_test.cpp @@ -7,9 +7,9 @@ int main() { std::string const input = "3,1416 xyz "; double result; - fast_float::parse_options options{fast_float::chars_format::general, ','}; auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options({fast_float::chars_format::general, ','})); if ((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; diff --git a/tests/fortran.cpp b/tests/fortran.cpp index 9167593..ca41986 100644 --- a/tests/fortran.cpp +++ b/tests/fortran.cpp @@ -9,11 +9,11 @@ int main_readme() { std::string const input = "1d+4"; double result; - fast_float::parse_options options{ - fast_float::chars_format::fortran | - fast_float::chars_format::allow_leading_plus}; auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options( + {fast_float::chars_format::fortran | + fast_float::chars_format::allow_leading_plus})); if ((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n" << result << "\n"; return EXIT_FAILURE; @@ -31,15 +31,15 @@ int main() { "1d-1", "1d-2", "1d-3", "1d-4"}; std::vector const fmt3{"+1+4", "+1+3", "+1+2", "+1+1", "+1+0", "+1-1", "+1-2", "+1-3", "+1-4"}; - fast_float::parse_options const options{ - fast_float::chars_format::fortran | - fast_float::chars_format::allow_leading_plus}; for (auto const &f : fmt1) { auto d{std::distance(&fmt1[0], &f)}; double result; - auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(), - result, options)}; + auto answer{fast_float::from_chars_advanced( + f.data(), f.data() + f.size(), result, + fast_float::parse_options( + {fast_float::chars_format::fortran | + fast_float::chars_format::allow_leading_plus}))}; if (answer.ec != std::errc() || result != expected[std::size_t(d)]) { std::cerr << "parsing failure on " << f << std::endl; return EXIT_FAILURE; @@ -49,8 +49,11 @@ int main() { for (auto const &f : fmt2) { auto d{std::distance(&fmt2[0], &f)}; double result; - auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(), - result, options)}; + auto answer{fast_float::from_chars_advanced( + f.data(), f.data() + f.size(), result, + fast_float::parse_options( + {fast_float::chars_format::fortran | + fast_float::chars_format::allow_leading_plus}))}; if (answer.ec != std::errc() || result != expected[std::size_t(d)]) { std::cerr << "parsing failure on " << f << std::endl; return EXIT_FAILURE; @@ -60,8 +63,11 @@ int main() { for (auto const &f : fmt3) { auto d{std::distance(&fmt3[0], &f)}; double result; - auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(), - result, options)}; + auto answer{fast_float::from_chars_advanced( + f.data(), f.data() + f.size(), result, + fast_float::parse_options( + {fast_float::chars_format::fortran | + fast_float::chars_format::allow_leading_plus}))}; if (answer.ec != std::errc() || result != expected[std::size_t(d)]) { std::cerr << "parsing failure on " << f << std::endl; return EXIT_FAILURE; diff --git a/tests/json_fmt.cpp b/tests/json_fmt.cpp index 1ba0d5a..70fdef2 100644 --- a/tests/json_fmt.cpp +++ b/tests/json_fmt.cpp @@ -7,11 +7,12 @@ int main_readme() { std::string const input = "+.1"; // not valid double result; - fast_float::parse_options options{ - fast_float::chars_format::json | - fast_float::chars_format::allow_leading_plus}; // should be ignored auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options options( + {fast_float::chars_format::json | + fast_float::chars_format::allow_leading_plus}) // should be ignored + ); if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; @@ -22,11 +23,12 @@ int main_readme() { int main_readme2() { std::string const input = "inf"; // not valid in JSON double result; - fast_float::parse_options options{ - fast_float::chars_format::json | - fast_float::chars_format::allow_leading_plus}; // should be ignored auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options options( + {fast_float::chars_format::json | + fast_float::chars_format::allow_leading_plus}) // should be ignored + ); if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; @@ -38,11 +40,12 @@ int main_readme3() { std::string const input = "inf"; // not valid in JSON but we allow it with json_or_infnan double result; - fast_float::parse_options options{ - fast_float::chars_format::json_or_infnan | - fast_float::chars_format::allow_leading_plus}; // should be ignored auto answer = fast_float::from_chars_advanced( - input.data(), input.data() + input.size(), result, options); + input.data(), input.data() + input.size(), result, + fast_float::parse_options options( + {fast_float::chars_format::json_or_infnan | + fast_float::chars_format::allow_leading_plus}); // should be ignored + ); if (answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; @@ -134,8 +137,9 @@ int main() { auto answer = fast_float::parse_number_string( f.data(), f.data() + f.size(), fast_float::parse_options( - fast_float::chars_format::json | - fast_float::chars_format::allow_leading_plus)); // should be ignored + {fast_float::chars_format::json | + fast_float::chars_format::allow_leading_plus})); // should be + // ignored if (answer.valid) { std::cerr << "json parse accepted invalid json " << f << std::endl; return EXIT_FAILURE;