Skip to content

Commit

Permalink
Merge pull request #220 from mayawarrier/main
Browse files Browse the repository at this point in the history
Add support for parsing numbers according to JSON format
  • Loading branch information
lemire authored Sep 15, 2023
2 parents cd5160a + 7b1fc2f commit e6b370d
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 7 deletions.
31 changes: 25 additions & 6 deletions include/fast_float/ascii_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,22 @@ parsed_number_string_t<UC> 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
++p;
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;
Expand All @@ -306,8 +312,16 @@ parsed_number_string_t<UC> 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<const UC>(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
Expand All @@ -323,8 +337,13 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
answer.fraction = span<const UC>(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
Expand Down
5 changes: 5 additions & 0 deletions include/fast_float/float_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
8 changes: 7 additions & 1 deletion include/fast_float/parse_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,13 @@ from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last,
}
parsed_number_string_t<UC> pns = parse_number_string<UC>(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
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 40 additions & 0 deletions tests/json_fmt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

#include <cstdlib>
#include <iostream>
#include <vector>

// test that this option is ignored
#define FASTFLOAT_ALLOWS_LEADING_PLUS

#include "fast_float/fast_float.h"

int main()
{
const std::vector<double> expected{ -0.2, 0.02, 0.002, 1., 0., std::numeric_limits<double>::infinity() };
const std::vector<std::string> accept{ "-0.2", "0.02", "0.002", "1e+0000", "0e-2", "inf" };
const std::vector<std::string> 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;
}

0 comments on commit e6b370d

Please sign in to comment.