diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 9afcdc4e..9653889a 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -280,7 +280,7 @@ parsed_number_string_t parse_number_string(UC const *p, UC const * pend, par answer.too_many_digits = false; answer.negative = (*p == UC('-')); #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default - if ((*p == UC('-')) || (*p == UC('+'))) { + if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) { #else if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here #endif @@ -288,8 +288,14 @@ parsed_number_string_t parse_number_string(UC const *p, UC const * pend, par if (p == pend) { return answer; } - if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot - return answer; + if (fmt & FASTFLOAT_JSONFMT) { + if (!is_integer(*p)) { // a sign must be followed by an integer + return answer; + } + } else { + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } } } UC const * const start_digits = p; @@ -306,8 +312,16 @@ parsed_number_string_t parse_number_string(UC const *p, UC const * pend, par 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)); + if (fmt & FASTFLOAT_JSONFMT) { + // at least 1 digit in integer part, without leading zeros + if (digit_count == 0 || (start_digits[0] == UC('0') && digit_count > 1)) { + return answer; + } + } + int64_t exponent = 0; - if ((p != pend) && (*p == decimal_point)) { + const bool has_decimal_point = (p != pend) && (*p == decimal_point); + if (has_decimal_point) { ++p; UC const * before = p; // can occur at most twice without overflowing, but let it occur more, since @@ -323,8 +337,13 @@ parsed_number_string_t parse_number_string(UC const *p, UC const * pend, par answer.fraction = span(before, size_t(p - before)); digit_count -= exponent; } - // we must have encountered at least one integer! - if (digit_count == 0) { + if (fmt & FASTFLOAT_JSONFMT) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return answer; + } + } + else if (digit_count == 0) { // we must have encountered at least one integer! return answer; } int64_t exp_number = 0; // explicit exponential part diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 4a290f48..2ad9e8c2 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -12,10 +12,15 @@ namespace fast_float { +#define FASTFLOAT_JSONFMT (1 << 5) + enum chars_format { scientific = 1 << 0, fixed = 1 << 2, hex = 1 << 3, + no_infnan = 1 << 4, + json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan, + json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific, general = fixed | scientific }; diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index e077b9d0..a011a8cb 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -164,7 +164,13 @@ from_chars_result_t from_chars_advanced(UC const * first, UC const * last, } parsed_number_string_t pns = parse_number_string(first, last, options); if (!pns.valid) { - return detail::parse_infnan(first, last, value); + if (options.format & chars_format::no_infnan) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } else { + return detail::parse_infnan(first, last, value); + } } answer.ec = std::errc(); // be optimistic diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 37f6c7f8..e58bea8e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -72,6 +72,8 @@ fast_float_add_cpp_test(long_test) fast_float_add_cpp_test(powersoffive_hardround) fast_float_add_cpp_test(string_test) +fast_float_add_cpp_test(json_fmt) + option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF) diff --git a/tests/json_fmt.cpp b/tests/json_fmt.cpp new file mode 100644 index 00000000..bdd32d94 --- /dev/null +++ b/tests/json_fmt.cpp @@ -0,0 +1,40 @@ + +#include +#include +#include + +// test that this option is ignored +#define FASTFLOAT_ALLOWS_LEADING_PLUS + +#include "fast_float/fast_float.h" + +int main() +{ + const std::vector expected{ -0.2, 0.02, 0.002, 1., 0., std::numeric_limits::infinity() }; + const std::vector accept{ "-0.2", "0.02", "0.002", "1e+0000", "0e-2", "inf" }; + const std::vector reject{ "-.2", "00.02", "0.e+1", "00.e+1", ".25", "+0.25", "inf", "nan(snan)" }; + + for (std::size_t i = 0; i < accept.size(); ++i) + { + const auto& f = accept[i]; + double result; + auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, fast_float::chars_format::json_or_infnan); + if (answer.ec != std::errc() || result != expected[i]) { + std::cerr << "json fmt rejected valid json " << f << std::endl; + return EXIT_FAILURE; + } + } + + for (std::size_t i = 0; i < reject.size(); ++i) + { + const auto& f = reject[i]; + double result; + auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, fast_float::chars_format::json); + if (answer.ec == std::errc()) { + std::cerr << "json fmt accepted invalid json " << f << std::endl; + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} \ No newline at end of file