From 3e2da540efb9c89271f1a2a4d6d2acfcc5fc34cf Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 19 Jan 2023 20:28:10 -0500 Subject: [PATCH] Support rccpfastfloat. --- include/fast_float/ascii_number.h | 4 + include/fast_float/float_common.h | 22 +++++- include/fast_float/parse_number.h | 5 ++ tests/CMakeLists.txt | 2 +- tests/rcppfastfloat_test.cpp | 123 ++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 tests/rcppfastfloat_test.cpp diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 1783bd4a..ee376649 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -93,7 +93,11 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_ answer.valid = false; answer.too_many_digits = false; answer.negative = (*p == '-'); +#if FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if ((*p == '-') || (*p == '+')) { +#else if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here +#endif ++p; if (p == pend) { return answer; diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 27497e42..037cb044 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -81,12 +81,11 @@ #endif #ifndef FASTFLOAT_ASSERT -#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } +#define FASTFLOAT_ASSERT(x) { ((void)(x)); } #endif #ifndef FASTFLOAT_DEBUG_ASSERT -#include -#define FASTFLOAT_DEBUG_ASSERT(x) assert(x) +#define FASTFLOAT_DEBUG_ASSERT(x) { ((void)(x)); } #endif // rust style `try!()` macro, or `?` operator @@ -453,6 +452,23 @@ fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &va #endif } +#if FASTFLOAT_SKIP_WHITE_SPACE // disabled by default +inline bool is_space(uint8_t c) { + static const bool table[] = { + 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, + 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, 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, 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, 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, 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}; + return table[c]; + } +#endif } // namespace fast_float #endif diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 4e646cac..2493b02f 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -136,6 +136,11 @@ from_chars_result from_chars_advanced(const char *first, const char *last, from_chars_result answer; +#if FASTFLOAT_SKIP_WHITE_SPACE // disabled by default + while ((first != last) && fast_float::is_space(uint8_t(*first))) { + first++; + } +#endif if (first == last) { answer.ec = std::errc::invalid_argument; answer.ptr = first; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b4a5767c..5f1947d6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,7 +56,7 @@ function(fast_float_add_cpp_test TEST_NAME) endif() endfunction(fast_float_add_cpp_test) - +fast_float_add_cpp_test(rcppfastfloat_test) fast_float_add_cpp_test(example_test) fast_float_add_cpp_test(example_comma_test) fast_float_add_cpp_test(basictest) diff --git a/tests/rcppfastfloat_test.cpp b/tests/rcppfastfloat_test.cpp new file mode 100644 index 00000000..8527da16 --- /dev/null +++ b/tests/rcppfastfloat_test.cpp @@ -0,0 +1,123 @@ +/** + * See https://github.com/eddelbuettel/rcppfastfloat/issues/4 + */ + +#define FASTFLOAT_ALLOWS_LEADING_PLUS 1 +#define FASTFLOAT_SKIP_WHITE_SPACE 1 // important ! +#include "fast_float/fast_float.h" +#include +#include +#include + +bool eddelbuettel() { + std::vector inputs = {"infinity", + " \r\n\t\f\v3.16227766016838 \r\n\t\f\v", + " \r\n\t\f\v3 \r\n\t\f\v", + " 1970-01-01", + "-NaN", + "-inf", + " \r\n\t\f\v2.82842712474619 \r\n\t\f\v", + "nan", + " \r\n\t\f\v2.44948974278318 \r\n\t\f\v", + "Inf", + " \r\n\t\f\v2 \r\n\t\f\v", + "-infinity", + " \r\n\t\f\v0 \r\n\t\f\v", + " \r\n\t\f\v1.73205080756888 \r\n\t\f\v", + " \r\n\t\f\v1 \r\n\t\f\v", + " \r\n\t\f\v1.4142135623731 \r\n\t\f\v", + " \r\n\t\f\v2.23606797749979 \r\n\t\f\v", + "1970-01-02 ", + " \r\n\t\f\v2.64575131106459 \r\n\t\f\v", + "inf", + "-nan", + "NaN", + "", + "-Inf", + "+2.2"}; + std::vector> expected_results = { + {true, std::numeric_limits::infinity()}, + {true, 3.16227766016838}, + {true, 3}, + {false, -1}, + {true, std::numeric_limits::quiet_NaN()}, + {true, -std::numeric_limits::infinity()}, + {true, 2.82842712474619}, + {true, std::numeric_limits::quiet_NaN()}, + {true, 2.44948974278318}, + {true, std::numeric_limits::infinity()}, + {true, 2}, + {true, -std::numeric_limits::infinity()}, + {true, 0}, + {true, 1.73205080756888}, + {true, 1}, + {true, 1.4142135623731}, + {true, 2.23606797749979}, + {false, -1}, + {true, 2.64575131106459}, + {true, std::numeric_limits::infinity()}, + {true, std::numeric_limits::quiet_NaN()}, + {true, std::numeric_limits::quiet_NaN()}, + {false, -1}, + {true, -std::numeric_limits::infinity()}, + {true, 2.2}}; + for (size_t i = 0; i < inputs.size(); i++) { + std::string &input = inputs[i]; + std::pair expected = expected_results[i]; + double result; + // answer contains a error code and a pointer to the end of the + // parsed region (on success). + auto answer = fast_float::from_chars(input.data(), + input.data() + input.size(), result); + if (answer.ec != std::errc()) { + std::cout << "could not parse" << std::endl; + if (expected.first) { + return false; + } + continue; + } + bool non_space_trailing_content = false; + if (answer.ptr != input.data() + input.size()) { + // check that there is no content left + for (const char *leftover = answer.ptr; + leftover != input.data() + input.size(); leftover++) { + if (!fast_float::is_space(uint8_t(*leftover))) { + non_space_trailing_content = true; + break; + } + } + } + if (non_space_trailing_content) { + std::cout << "found trailing content " << std::endl; + } + + if (non_space_trailing_content) { + if (!expected.first) { + continue; + } else { + return false; + } + } + std::cout << "parsed " << result << std::endl; + if (!expected.first) { + return false; + } + if (result != expected.second) { + if (std::isnan(result) && std::isnan(expected.second)) { + continue; + } + std::cout << "results do not match. Expected "<< expected.second << std::endl; + return false; + } + } + return true; +} + +int main() { + if (!eddelbuettel()) { + printf("Bug.\n"); + return EXIT_FAILURE; + } + printf("All ok.\n"); + return EXIT_SUCCESS; +}