diff --git a/CMakeLists.txt b/CMakeLists.txt index 70201f51b..767a8dc33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,4 +87,11 @@ else() PUBLIC Boost::system ) + option(BUILD_TESTING "Build the tests" ON) + if (BUILD_TESTING) + add_subdirectory(bench) + add_subdirectory(example) + add_subdirectory(test) + endif() + endif() diff --git a/include/boost/json/detail/impl/number.ipp b/include/boost/json/detail/impl/number.ipp index a39130242..d8a19e23b 100644 --- a/include/boost/json/detail/impl/number.ipp +++ b/include/boost/json/detail/impl/number.ipp @@ -17,6 +17,11 @@ namespace boost { namespace json { namespace detail { +/* References + + https://ampl.com/netlib/fp/dtoa.c + +*/ inline double pow10(int exp) noexcept @@ -92,9 +97,16 @@ pow10(int exp) noexcept 1e+300, 1e+301, 1e+302, 1e+303, 1e+304, 1e+305, 1e+306, 1e+307, 1e+308 }; - exp += 308; - BOOST_ASSERT(exp >= 0 && exp < 618); - return tab[exp]; + if (exp < -308 || exp > 308) + { + return std::pow(10.0, exp); + } + else + { + exp += 308; + BOOST_ASSERT(exp >= 0 && exp < 618); + return tab[exp]; + } } // return true on '-' '0' '1'..'9'. @@ -109,7 +121,8 @@ maybe_init(char ch) noexcept n_.kind = kind::int64; exp_ = 0; dig_ = 0; - off_ = 0; + pos_ = -1; + sig_ = 0; neg_ = true; st_ = state::init0; return true; @@ -119,7 +132,8 @@ maybe_init(char ch) noexcept return false; n_.u = d; exp_ = 0; - off_ = 0; + pos_ = -1; + sig_ = 0; neg_ = false; n_.kind = kind::int64; if(ch == '0') @@ -130,6 +144,7 @@ maybe_init(char ch) noexcept else { dig_ = 1; + sig_ = 1; st_ = state::mant; } return true; @@ -166,9 +181,10 @@ loop: // [0,1..9] case state::init0: { + // got minus sign + BOOST_ASSERT(neg_); BOOST_ASSERT( n_.kind == kind::int64); - BOOST_ASSERT(neg_); if(p >= p1) break; unsigned char const d = *p - '0'; @@ -185,12 +201,15 @@ loop: } n_.u = d; dig_ = 1; + sig_ = 1; st_ = state::mantn; goto loop; } // [.eE] case state::init1: + { + // got leading 0 BOOST_ASSERT( n_.kind == kind::int64); if(p >= p1) @@ -198,31 +217,96 @@ loop: if(*p == 'e' || *p == 'E') { ++p; - n_.d = 0; n_.kind = kind::double_; st_ = state::exp1; goto loop; } if(*p == '.') { + BOOST_ASSERT(pos_ < 0); + BOOST_ASSERT(dig_ == 0); ++p; - st_ = state::frac1; + pos_ = 0; + st_ = state::mantf; + n_.kind = kind::double_; goto loop; } - // zero - n_.u = 0; + unsigned char const d = *p - '0'; + if(d < 10) + { + ec = error::expected_fraction; + goto finish; + } + // reached end of number st_ = state::end; goto finish; + } + + // 1*digit + case state::mantf: + { + if (p >= p1) + break; + unsigned char const d = *p - '0'; + if(d < 10) + { + if ( d == 0 && n_.u == 0) + { + st_ = state::zeroes; + ++p; + ++dig_; + } + else if(neg_) + { + st_ = state::mantn; + } + else + { + st_ = state::mant; + } + goto loop; + } + + ec = error::expected_fraction; + goto finish; + } + + // *[0] + case state::zeroes: + { + BOOST_ASSERT(pos_ == 0); + while(p < p1) + { + unsigned char const d = *p - '0'; + if(d == 0) + { + ++p; + ++dig_; + continue; + } + if(d < 10 || + *p == 'e' || *p == 'E') + { + if(neg_) + st_ = state::mantn; + else + st_ = state::mant; + goto loop; + } + // reached end of number + st_ = state::end; + goto finish; + } + break; + } //----------------------------------- // *[0..9] case state::mant: { - BOOST_ASSERT( - n_.kind == kind::int64); BOOST_ASSERT(! neg_); - if(p < p1) + if(p < p1) // VFALCO see if the compiler can do this for us { auto m = n_.u; do @@ -234,32 +318,39 @@ loop: if( m > 1844674407370955161 || ( m == 1844674407370955161 && d > 5)) { - ++p; - n_.d = static_cast(m) * 10; - n_.kind = kind::double_; - st_ = state::mantd; - goto loop; + n_.u = m; + goto enter_mantd; } ++p; + // VFALCO Check dig_ for overflow + // Could use an implementation-defined limit + // which is lower than USHRT_MAX ++dig_; + ++sig_; m = 10 * m + d; continue; } - if(*p == '.') + if(*p == '.' && pos_ < 0) { ++p; + pos_ = dig_; + n_.kind = kind::double_; + st_ = state::mantf; n_.u = m; - st_ = state::frac1; goto loop; } if(*p == 'e' || *p == 'E') { + // treat 'E' as '.' + if (pos_ < 0) + pos_ = dig_; ++p; - n_.d = static_cast(m); + n_.u = m; n_.kind = kind::double_; st_ = state::exp1; goto loop; } + // reached end of number n_.u = m; finish(ec); goto finish; @@ -273,9 +364,8 @@ loop: // *[0..9] (negative) case state::mantn: { - BOOST_ASSERT(n_.kind == kind::int64); BOOST_ASSERT(neg_); - if(p < p1) + if(p < p1) // VFALCO see if the compiler can do this for us { auto m = n_.u; do @@ -287,33 +377,41 @@ loop: if( m > 922337203685477580 || ( m == 922337203685477580 && d > 8)) { - n_.d = static_cast(m); - n_.kind = kind::double_; - st_ = state::mantd; - goto loop; + n_.u = m; + goto enter_mantd; } - m = 10 * m + d; - ++dig_; ++p; + // VFALCO Check dig_ for overflow + // Could use an implementation-defined limit + // which is lower than USHRT_MAX + ++sig_; + ++dig_; + m = 10 * m + d; continue; } - if(*p == '.') + if(*p == '.' && pos_ < 0) { ++p; - st_ = state::frac1; + pos_ = dig_; + n_.kind = kind::double_; + n_.u = m; + st_ = state::mantf; goto loop; } if(*p == 'e' || *p == 'E') { + // treat 'E' as '.' + if (pos_ < 0) + pos_ = dig_; ++p; - n_.d = static_cast(m); + n_.u = m; n_.kind = kind::double_; st_ = state::exp1; goto loop; } - n_.i = static_cast< - int64_t>(~n_.u+1); - st_ = state::end; + // reached end of number + n_.u = m; + finish(ec); goto finish; } while(p < p1); @@ -322,147 +420,59 @@ loop: break; } + enter_mantd: + // make sure we are past the + // limit of double precision. + BOOST_ASSERT(sig_ == dig_); + BOOST_ASSERT(dig_ >= 18); + ++p; + // VFALCO Check dig_ for overflow + // Could use an implementation-defined limit + // which is lower than USHRT_MAX + ++dig_; + st_ = state::mantd; + n_.kind = kind::double_; + BOOST_FALLTHROUGH; + // *[0..9] (double) case state::mantd: { - BOOST_ASSERT( - n_.kind == kind::double_); - auto d = n_.d; while(p < p1) { - if(*p == '.') + if(*p == '.' && pos_ < 0) { ++p; - n_.d = d; - st_ = state::fracd; - goto loop; + pos_ = dig_; + continue; } if(*p == 'e' || *p == 'E') { + // treat 'E' as '.' + if (pos_ < 0) + pos_ = dig_; ++p; - n_.d = d; st_ = state::exp1; goto loop; } - if(static_cast( - *p - '0') > 9) + unsigned char const d = *p - '0'; + if(d >= 10) { + // reached end of number finish(ec); goto finish; } ++p; - d = d * 10; - } - n_.d = d; - break; - } - - //----------------------------------- - - // [0..9] - case state::frac1: - { - BOOST_ASSERT( - n_.kind == kind::int64); - if(p >= p1) - break; - unsigned char const d = *p - '0'; - if(d >= 10) - { - ec = error::expected_fraction; - goto finish; - } - n_.kind = kind::double_; - st_ = state::frac2; - BOOST_FALLTHROUGH; - } - - // zero or more [0..9] - case state::frac2: - { - BOOST_ASSERT( - n_.kind == kind::double_); - if(p < p1) - { - auto m = n_.u; - do - { - unsigned char const d = *p - '0'; - if(d < 10) - { - if(m > 9007199254740991) // (2^53-1) - { - ++p; - n_.d = static_cast(m); - st_ = state::fracd; - goto loop; - } - ++p; - m = 10 * m + d; - --off_; - if(m != 0) - ++dig_; - continue; - } - if(*p != 'e' && *p != 'E') - { - n_.u = m; - finish(ec); - goto finish; - } - ++p; - st_ = state::exp1; - goto loop; - } - while(p < p1); - n_.u = m; - } - break; - } - - // zero or more [0..9] (double) - case state::fracd: - { - BOOST_ASSERT( - n_.kind == kind::double_); - if(p < p1) - { - auto m = n_.d; - do - { - unsigned char const d = *p - '0'; - if(d < 10) - { - if(dig_ < 17) - { - m = 10 * m + d; - --off_; - if(m > 0) - ++dig_; - } - ++p; - continue; - } - if(*p != 'e' && *p != 'E') - { - n_.d = m; - finish(ec); - goto finish; - } - ++p; - n_.d = m; - st_ = state::exp1; - goto loop; - } - while(p < p1); - n_.d = m; + // VFALCO Check dig_ for overflow + // Could use an implementation-defined limit + // which is lower than USHRT_MAX + ++dig_; } break; } //----------------------------------- - // + or - + // [-+,0..9] case state::exp1: { BOOST_ASSERT( @@ -490,7 +500,7 @@ loop: if(p >= p1) break; unsigned char const d = *p - '0'; - if(d > 9) + if(d >= 10) { ec = error::expected_exponent; goto finish; @@ -506,7 +516,8 @@ loop: { if(p < p1) { - auto const lim = 308 - off_; + // VFALCO FIX + auto const lim = 700;//308 - off_; auto e = exp_; while(p < p1) { @@ -575,68 +586,101 @@ finish( break; case state::init1: + BOOST_ASSERT(n_.u == 0); BOOST_ASSERT( n_.kind == kind::int64); - BOOST_ASSERT(n_.i == 0); //ec = {}; st_ = state::end; break; - case state::mant: - BOOST_ASSERT( - n_.kind == kind::int64); - BOOST_ASSERT(! neg_); - //ec = {}; - if(n_.u <= INT64_MAX) - n_.i = static_cast< - int64_t>(n_.u); + case state::zeroes: + BOOST_ASSERT(n_.u == 0); + if(pos_ == dig_) + { + ec = error::expected_fraction; + break; + } + if(pos_ >= 0) + { + BOOST_ASSERT( + n_.kind == kind::double_); + if(neg_) + n_.d = -0.0; + else + n_.d = 0; + } else - n_.kind = kind::uint64; - st_ = state::end; - break; - - case state::mantn: - BOOST_ASSERT( - n_.kind == kind::int64); - BOOST_ASSERT(neg_); - //ec = {}; - n_.i = static_cast< - int64_t>(~n_.u+1); - st_ = state::end; - break; - - case state::mantd: - //ec = {}; - if(neg_) - n_.d = -n_.d; - exp_ += off_; - n_.d *= pow10(exp_); + { + BOOST_ASSERT( + n_.kind == kind::int64); + n_.i = 0; + } st_ = state::end; break; - case state::frac1: + case state::mantf: ec = error::expected_fraction; break; - case state::frac2: - BOOST_ASSERT( - n_.kind == kind::double_); - //ec = {}; - exp_ += off_; - n_.d = n_.u * pow10(exp_); - if(neg_) - n_.d = -n_.d; + case state::mant: + BOOST_ASSERT(! neg_); + BOOST_FALLTHROUGH; + case state::mantn: + BOOST_ASSERT(dig_ > 0); + if(pos_ == dig_) + { + ec = error::expected_fraction; + break; + } + if(n_.kind == kind::double_) + { + if( pos_ < 0) + pos_ = dig_; + if(neg_) + n_.d = (-static_cast< + double>(n_.u)) * + pow10(pos_ - dig_); + else + n_.d = static_cast< + double>(n_.u) * + pow10(pos_ - dig_); + } + else + { + BOOST_ASSERT( + n_.kind == kind::int64); + if(st_ == state::mantn) + { + n_.i = static_cast< + int64_t>(~n_.u+1); + } + else + { + if( n_.u <= INT64_MAX) + n_.i = static_cast< + int64_t>(n_.u); + else + n_.kind = kind::uint64; + } + } st_ = state::end; break; - case state::fracd: + case state::mantd: BOOST_ASSERT( n_.kind == kind::double_); - //ec = {}; - exp_ += off_; - n_.d = n_.d * pow10(exp_); + if(pos_ == dig_) + { + ec = error::expected_fraction; + break; + } + if( pos_ < 0) + pos_ = dig_; + n_.d = static_cast(n_.u) * + pow10(pos_ - sig_); if(neg_) n_.d = -n_.d; + n_.d *= pow10(exp_); st_ = state::end; break; @@ -649,18 +693,31 @@ finish( break; case state::exp3: - //ec = {}; - exp_ += off_; - if(eneg_) + { + if (eneg_) exp_ = -exp_; - n_.d = n_.d * pow10(exp_); - if(neg_) + + if (pos_ == 0) + { + // abs(mantissa) < 1 + exp_ = static_cast( + exp_ + sig_ - dig_ - 1); + } + else + { + // abs(mantissa) >= 1 + exp_ = static_cast( + exp_ + pos_ - sig_); + } + + n_.d = static_cast(n_.u) * + pow10(exp_); + if (neg_) n_.d = -n_.d; st_ = state::end; break; - + } case state::end: - //ec = {}; break; } } diff --git a/include/boost/json/detail/number.hpp b/include/boost/json/detail/number.hpp index 96ec24a08..8f6efb07c 100644 --- a/include/boost/json/detail/number.hpp +++ b/include/boost/json/detail/number.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace boost { @@ -38,16 +39,18 @@ class number_parser enum class state { init, init0, init1, + mantf, + zeroes, mant, mantn, mantd, - frac1, frac2, fracd, exp1, exp2, exp3, end }; number n_; - short exp_; - short dig_; - short off_; + short exp_; // exponent string as integer + short dig_; // digits in mantissa + short pos_; // position of decimal point + short sig_; // significant digits in mantissa bool neg_; bool eneg_; state st_; diff --git a/include/boost/json/detail/string_impl.hpp b/include/boost/json/detail/string_impl.hpp index a4d90067f..80e1823ca 100644 --- a/include/boost/json/detail/string_impl.hpp +++ b/include/boost/json/detail/string_impl.hpp @@ -58,7 +58,7 @@ class string_impl struct sbo { - kind k; + kind k; // must come first char buf[sbo_chars_ + 1]; }; diff --git a/test/_detail_number.cpp b/test/_detail_number.cpp index b74aa3dc4..d7f76ffa5 100644 --- a/test/_detail_number.cpp +++ b/test/_detail_number.cpp @@ -10,18 +10,147 @@ // Test that header file is self-contained. #include +#include +#include #include #include "test_suite.hpp" +#define ACCURATE_CONVERSION 0 + namespace boost { namespace json { namespace detail { -class number_parser_test +bool +operator==( + number const& lhs, + number const& rhs) noexcept +{ + if(lhs.kind != rhs.kind) + return false; + switch(lhs.kind) + { + case json::kind::int64: + return lhs.i == rhs.i; + case json::kind::uint64: + return lhs.u == rhs.u; + default: + break; + } + return + std::signbit(lhs.d) == + std::signbit(rhs.d) && + lhs.d == rhs.d; +} + +struct double_diagnoser +{ + + static void emit_hex(std::ostream& os, double d) + { + std::uint64_t binary; + static_assert(sizeof(binary) == sizeof(d), ""); + + std::memcpy(&binary, &d, sizeof(d)); + auto oldflags = os.flags(); + + try + { + os << std::hex << std::setw(16) << std::setfill('0') << binary; + os.flags(oldflags); + } + catch(...) + { + os.flags(oldflags); + } + } + static void emit_pow2(std::ostream& os, double d) + { + auto oldflags = os.flags(); + try + { + int exponent = 0; + auto mantissa = std::frexp(d, &exponent); + os << std::fixed << mantissa; + os << " *2^ "; + os << exponent; + os.flags(oldflags); + } + catch(...) + { + os.flags(oldflags); + throw; + } + } + + static void emit_scientific(std::ostream& os, double d) + { + auto oldflags = os.flags(); + try + { + os + << std::setprecision(std::numeric_limits::max_digits10) + << d; + os.flags(oldflags); + } + catch(...) + { + os.flags(oldflags); + throw; + } + } + + friend + std::ostream& + operator<<(std::ostream& os, double_diagnoser diag) + { + emit_scientific(os, diag.d); + os << "(0x"; + emit_hex(os, diag.d); + os << ") "; + emit_pow2(os, diag.d); + return os; + } + + double d; +}; + +auto diagnose(double d) -> double_diagnoser +{ + return double_diagnoser { d }; +} + +bool +are_close( + double x, + double y) +{ + std::uint64_t bx, by; + std::memcpy(&bx, &x, sizeof(x)); + std::memcpy(&by, &y, sizeof(y)); + + auto diff = bx - by; + switch (diff) + { + case 0: + case 1: + case 0xffffffffffffffff: + return true; + default: + break; + } + return false; +} + + +class number_test { public: + test_suite::log_type log; + template + static void grind( string_view s, @@ -71,6 +200,49 @@ class number_parser_test } } + //------------------------------------------------------ + + void + testMembers() + { + // maybe_init + { + number_parser p; + BOOST_TEST(! p.maybe_init(0)); + BOOST_TEST(! p.maybe_init('A')); + BOOST_TEST(! p.maybe_init('a')); + BOOST_TEST(! p.maybe_init('.')); + BOOST_TEST(! p.maybe_init('!')); + BOOST_TEST(! p.maybe_init(' ')); + BOOST_TEST(p.maybe_init('0')); p.reset(); + BOOST_TEST(p.maybe_init('1')); p.reset(); + BOOST_TEST(p.maybe_init('2')); p.reset(); + BOOST_TEST(p.maybe_init('3')); p.reset(); + BOOST_TEST(p.maybe_init('4')); p.reset(); + BOOST_TEST(p.maybe_init('5')); p.reset(); + BOOST_TEST(p.maybe_init('6')); p.reset(); + BOOST_TEST(p.maybe_init('7')); p.reset(); + BOOST_TEST(p.maybe_init('8')); p.reset(); + BOOST_TEST(p.maybe_init('9')); p.reset(); + BOOST_TEST(p.maybe_init('0')); p.reset(); + BOOST_TEST(p.maybe_init('-')); p.reset(); + } + + // finish + { + error_code ec; + number_parser p; + p.write_some("0x", 2, ec); + if(BOOST_TEST(! ec)) + { + p.finish(ec); + BOOST_TEST(! ec); + } + } + } + + //------------------------------------------------------ + void check_bad(string_view s) { @@ -104,7 +276,7 @@ class number_parser_test int64_t i) { grind(s, - [&](detail::number num) + [&](number num) { if( BOOST_TEST( num.kind == kind::int64)) @@ -118,7 +290,7 @@ class number_parser_test uint64_t u) { grind(s, - [&](detail::number num) + [&](number num) { if( BOOST_TEST( num.kind == kind::uint64)) @@ -127,26 +299,8 @@ class number_parser_test } void - check_double( - string_view s, - double d) - { - grind(s, - [&](detail::number num) - { - if( BOOST_TEST( - num.kind == kind::double_)) - BOOST_TEST(num.d == d); - }); - } - - void - testParse() + testIntegers() { - check_double("-999999999999999999999", -999999999999999999999.0); - check_double("-100000000000000000009", -100000000000000000009.0); - check_double("-10000000000000000000", -10000000000000000000.0); - check_double("-9223372036854775809", -9223372036854775809.0); check_int64( "-9223372036854775808", INT64_MIN); check_int64( "-9223372036854775807", -9223372036854775807); check_int64( "-999999999999999999", -999999999999999999); @@ -187,155 +341,374 @@ class number_parser_test check_int64( "99999999999999999", 99999999999999999); check_int64( "999999999999999999", 999999999999999999); check_int64( "9223372036854775807", INT64_MAX); + check_uint64( "9223372036854775808", 9223372036854775808ULL); check_uint64( "9999999999999999999", 9999999999999999999ULL); check_uint64( "18446744073709551615", UINT64_MAX); - check_double( "18446744073709551616", 18446744073709551616.0); - check_double( "99999999999999999999", 99999999999999999999.0); - check_double( "999999999999999999999", 999999999999999999999.0); - check_double( "1000000000000000000000", 1000000000000000000000.0); - check_double( "9999999999999999999999", 9999999999999999999999.0); - check_double( "99999999999999999999999", 99999999999999999999999.0); - - check_double("-0.9999999999999999999999", -1.0000000000000002); - check_double("-0.9999999999999999", -1.0000000000000000); - check_double("-0.9007199254740991", -0.9007199254740991); // (2^53-1) - check_double("-0.999999999999999", -0.99999999999999911); - check_double("-0.99999999999999", -0.99999999999999001); - check_double("-0.9999999999999", -0.99999999999990008); - check_double("-0.999999999999", -0.99999999999900002); - check_double("-0.99999999999", -0.99999999998999989); - check_double("-0.9999999999", -0.99999999989999999); - check_double("-0.999999999", -0.99999999900000003); - check_double("-0.99999999", -0.99999999000000006); - check_double("-0.9999999", -0.99999989999999994); - check_double("-0.999999", -0.999999); - check_double("-0.99999", -0.99999); - check_double("-0.9999", -0.9999); - check_double("-0.8125", -0.8125); - check_double("-0.999", -0.999); - check_double("-0.99", -0.99); - check_double("-1.0", -1); - check_double("-0.9", -0.9); - check_double("-0.0", 0); - check_double( "0.0", 0); - check_double( "0.9", 0.9); - check_double( "0.99", 0.99); - check_double( "0.999", 0.999); - check_double( "0.8125", 0.8125); - check_double( "0.9999", 0.9999); - check_double( "0.99999", 0.99999); - check_double( "0.999999", 0.999999); - check_double( "0.9999999", 0.99999989999999994); - check_double( "0.99999999", 0.99999999000000006); - check_double( "0.999999999", 0.99999999900000003); - check_double( "0.9999999999", 0.99999999989999999); - check_double( "0.99999999999", 0.99999999998999989); - check_double( "0.999999999999", 0.99999999999900002); - check_double( "0.9999999999999", 0.99999999999990008); - check_double( "0.99999999999999", 0.99999999999999001); - check_double( "0.999999999999999", 0.99999999999999911); - check_double( "0.9007199254740991", 0.9007199254740991); // (2^53-1) - check_double( "0.9999999999999999", 1.0000000000000000); - check_double( "0.9999999999999999999999", 1.0000000000000002); - check_double( "0.999999999999999999999999999", 1.0000000000000002); - - check_double("-1e308", -1e308); - check_double("-1e-308", -1e-308); - check_double("-9999e300", -9999e300); - check_double("-999e100", -999e100); - check_double("-99e10", -99e10); - check_double("-9e1", -9e1); - check_double( "9e1", 9e1); - check_double( "99e10", 99e10); - check_double( "999e100", 999e100); - check_double( "9999e300", 9999e300); - check_double( "999999999999999999.0", 999999999999999999.0); - check_double( "999999999999999999999.0", 999999999999999999999.0); - check_double( "999999999999999999999e5", 999999999999999999999e5); - check_double( "999999999999999999999.0e5", 999999999999999999999.0e5); - - check_double( "0.00000000000000001", 0.00000000000000001); - - check_double("-1e-1", -1e-1); - check_double("-1e0", -1); - check_double("-1e1", -1e1); - check_double( "0e0", 0); - check_double( "1e0", 1); - check_double( "1e10", 1e10); + } + void + testBad() + { check_bad(""); check_bad("x"); - check_bad("00"); check_bad("e"); check_bad("1ex"); check_bad("-"); - check_bad("00"); - check_bad("00."); - check_bad("00.0"); check_bad("1a"); check_bad("."); + check_bad("-."); check_bad("1."); + check_bad("-1."); check_bad("1.x"); check_bad("1+"); + check_bad("1-"); check_bad("0.0+"); check_bad("0.0e+"); check_bad("0.0e-"); check_bad("0.0e0-"); check_bad("0.0e"); + check_bad("0.e1"); check_bad("-e"); check_bad("-x"); + check_bad("2.e+3"); + check_bad("-2.e+3"); + + // leading 0 must be followed by [.eE] or nothing + check_bad( "00"); + check_bad( "01"); + check_bad( "00."); + check_bad( "00.0"); + check_bad("-00"); + check_bad("-01"); + check_bad("-00."); + check_bad("-00.0"); } - void - testMembers() + //------------------------------------------------------ + + struct f_boost { - // maybe_init + static + string_view + name() noexcept { - number_parser p; - BOOST_TEST(! p.maybe_init(0)); - BOOST_TEST(! p.maybe_init('A')); - BOOST_TEST(! p.maybe_init('a')); - BOOST_TEST(! p.maybe_init('.')); - BOOST_TEST(! p.maybe_init('!')); - BOOST_TEST(! p.maybe_init(' ')); - BOOST_TEST(p.maybe_init('0')); p.reset(); - BOOST_TEST(p.maybe_init('1')); p.reset(); - BOOST_TEST(p.maybe_init('2')); p.reset(); - BOOST_TEST(p.maybe_init('3')); p.reset(); - BOOST_TEST(p.maybe_init('4')); p.reset(); - BOOST_TEST(p.maybe_init('5')); p.reset(); - BOOST_TEST(p.maybe_init('6')); p.reset(); - BOOST_TEST(p.maybe_init('7')); p.reset(); - BOOST_TEST(p.maybe_init('8')); p.reset(); - BOOST_TEST(p.maybe_init('9')); p.reset(); - BOOST_TEST(p.maybe_init('0')); p.reset(); - BOOST_TEST(p.maybe_init('-')); p.reset(); + return "boost"; } - // finish + double + operator()(string_view s) const { error_code ec; number_parser p; - p.write_some("0x", 2, ec); - if(BOOST_TEST(! ec)) + p.write(s.data(), s.size(), ec); + if(ec) + BOOST_THROW_EXCEPTION( + system_error(ec)); + BOOST_TEST(p.is_done()); + auto const num = p.get(); + BOOST_ASSERT( + num.kind == kind::double_); + + grind(s, + [&](number num1) + { + if( BOOST_TEST( + num1.kind == kind::double_)) + BOOST_TEST(num1.d == num.d); + }); + + return num.d; + } + }; + + // Verify that f converts to the + // same double produced by `strtod`. + // Requires `s` is not represented by an integral type. + template + void + fcheck(std::string const& s, F const& f) + { + char* str_end; + double const need = + std::strtod(s.c_str(), &str_end); + BOOST_TEST(str_end == &s.back() + 1); + double const got = f(s); + auto same = got == need; + +#if !ACCURATE_CONVERSION + auto close = same ? true : are_close(got, need); + if(! BOOST_TEST(close)) + { + log << "not close : " << + f.name() << "\n" + "string: " << s << "\n" + "need : " << diagnose(need) << "\n" + "got : " << diagnose(got) << + std::endl; + } +#else + if (!BOOST_TEST(same)) + { + log << "close but not close enough : " << + f.name() << "\n" + "string: " << s << "\n" + "need : " << diagnose(need) << "\n" + "got : " << diagnose(got) << + std::endl; + } +#endif + } + + template + void + check_numbers(F const& f) + { + auto const fc = + [&](std::string const& s) { - p.finish(ec); + fcheck(s, f); + }; + + fc("-999999999999999999999"); + fc("-100000000000000000009"); + fc("-10000000000000000000"); + fc("-9223372036854775809"); + + fc("18446744073709551616"); + fc("99999999999999999999"); + fc("999999999999999999999"); + fc("1000000000000000000000"); + fc("9999999999999999999999"); + fc("99999999999999999999999"); + + fc("-0.9999999999999999999999"); + fc("-0.9999999999999999"); + fc("-0.9007199254740991"); + fc("-0.999999999999999"); + fc("-0.99999999999999"); + fc("-0.9999999999999"); + fc("-0.999999999999"); + fc("-0.99999999999"); + fc("-0.9999999999"); + fc("-0.999999999"); + fc("-0.99999999"); + fc("-0.9999999"); + fc("-0.999999"); + fc("-0.99999"); + fc("-0.9999"); + fc("-0.8125"); + fc("-0.999"); + fc("-0.99"); + fc("-1.0"); + fc("-0.9"); + fc("-0.0"); + fc("0.0"); + fc("0.9"); + fc("0.99"); + fc("0.999"); + fc("0.8125"); + fc("0.9999"); + fc("0.99999"); + fc("0.999999"); + fc("0.9999999"); + fc("0.99999999"); + fc("0.999999999"); + fc("0.9999999999"); + fc("0.99999999999"); + fc("0.999999999999"); + fc("0.9999999999999"); + fc("0.99999999999999"); + fc("0.999999999999999"); + fc("0.9007199254740991"); + fc("0.9999999999999999"); + fc("0.9999999999999999999999"); + fc("0.999999999999999999999999999"); + + fc("-1e308"); + fc("-1e-308"); + fc("-9999e300"); + fc("-999e100"); + fc("-99e10"); + fc("-9e1"); + fc("9e1"); + fc("99e10"); + fc("999e100"); + fc("9999e300"); + fc("999999999999999999.0"); + fc("999999999999999999999.0"); + fc("999999999999999999999e5"); + fc("999999999999999999999.0e5"); + + fc("0.00000000000000001"); + + fc("-1e-1"); + fc("-1e0"); + fc("-1e1"); + fc("0e0"); + fc("1e0"); + fc("1e10"); + + fc("0." + "00000000000000000000000000000000000000000000000000" // 50 zeroes + "1e50"); + fc("-0." + "00000000000000000000000000000000000000000000000000" // 50 zeroes + "1e50"); + + fc("0." + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" // 500 zeroes + "1e600"); + fc("-0." + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" // 500 zeroes + "1e600"); + + fc("0e" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" // 500 zeroes + ); + } + + void + testDoubles() + { + check_numbers(f_boost{}); + } + + static + number + int64_num(int64_t i) noexcept + { + number num; + num.i = i; + num.kind = kind::int64; + return num; + } + + static + number + uint64_num(uint64_t u) noexcept + { + number num; + num.u = u; + num.kind = kind::uint64; + return num; + } + + static + number + double_num(double d) noexcept + { + number num; + num.d = d; + num.kind = kind::double_; + return num; + } + + void + testEdgeCases() + { + auto const parse = + [&](string_view s) + { + error_code ec; + number_parser p; + p.write(s.data(), s.size(), ec); BOOST_TEST(! ec); - } - } + return p.get(); + }; + + BOOST_TEST(parse("-0.0") == double_num(-0.0)); + BOOST_TEST(parse("-0E0") == double_num(-0.0)); + BOOST_TEST(parse("-0") == int64_num(0)); + + BOOST_TEST(parse("0") == int64_num(0)); + BOOST_TEST(parse("0.010") == double_num(0.01)); + BOOST_TEST(parse("-0.010") == double_num(-0.01)); + BOOST_TEST(parse("1.010") == double_num(1.01)); + BOOST_TEST(parse("-1.010") == double_num(-1.01)); } void run() { - testParse(); + testEdgeCases(); testMembers(); + testIntegers(); + testBad(); + testDoubles(); } }; -TEST_SUITE(number_parser_test, "boost.json.detail.number_parser"); +TEST_SUITE(number_test, "boost.json.detail.number"); } // detail } // json } // boost + +#if 0 + +(for positive) +A. accumulate digits into unsigned u + if(got('.')) + if( have_dot ) + return error; + dot_pos = pos + else if(u > UINT64_MAX) + goto state C + else + ++dig_; + accumulate digit + +(for negative) +B. accumulate digits into unsigned u + if(got('.')) + if( have_dot ) + return error; + dot_pos = pos + else if(u > abs(INT64_MIN)) + goto state C + else + ++dig_; + accumulate digit + +C. accumulate exponent offset + if(got('e', 'E', '-', '+') + ... + else if(got('.')) + if( have_dot ) + return error; + dot_pos = pos + else + if( have_dot ) + // do nothing + else + ++dig_; + +#endif diff --git a/test/basic_parser.cpp b/test/basic_parser.cpp index 650ca04be..eb0c879a2 100644 --- a/test/basic_parser.cpp +++ b/test/basic_parser.cpp @@ -472,7 +472,7 @@ class basic_parser_test testParser() { auto const check = - []( string_view s, + [this]( string_view s, bool is_done) { fail_parser p; @@ -481,7 +481,10 @@ class basic_parser_test s.data(), s.size(), ec); if(! BOOST_TEST(! ec)) + { + log << " failed to parse: " << s; return; + } BOOST_TEST(is_done == p.is_done()); }; @@ -507,7 +510,6 @@ class basic_parser_test check("0 ", false); check("0x", true); check("0 x", true); - check("00", true); check("0.", false); check("0.0", false); check("0.0 ", false); diff --git a/test/serializer.cpp b/test/serializer.cpp index 19573c03e..493c81d22 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -273,6 +273,8 @@ class serializer_test check("-999"); check("-99"); check("-9"); + check("-0"); + check("-0.0"); check( "0"); check( "9"); check( "99"); @@ -461,6 +463,21 @@ class serializer_test } } + void + testNumberRoundTrips() + { + BOOST_TEST(std::signbit(parse("-0.0").as_double())); + BOOST_TEST(to_string(value(-0.0)) == "-0E0"); + + //BOOST_TEST(parse("-0.0").as_double() == -0); + //BOOST_TEST(parse("-0").as_int64() == 0); + //BOOST_TEST(to_string(parse("0.0")) == "0"); + //BOOST_TEST(to_string(parse("-0.0")) == "-0.0"); + + // VFALCO Peter is unsure what this should do + //BOOST_TEST(to_string(parse("-0")) == "-0"); + } + void run() { @@ -472,6 +489,7 @@ class serializer_test testScalar(); testVectors(); testOstream(); + testNumberRoundTrips(); } };