From 0580d42d115b779a86aec5edc5dddd63f291568e Mon Sep 17 00:00:00 2001 From: miloyip Date: Wed, 3 Sep 2014 01:02:38 +0800 Subject: [PATCH] Fallback strtod() when not able to do fast-path This shall generate best possible precision (if strtod() is correctly implemented). Need more unit tests and performance tests. May add an option for accepting precision error. Otherwise LUT in Pow10() can be reduced. --- include/rapidjson/reader.h | 122 ++++++++++++++++++++--------------- test/unittest/readertest.cpp | 4 +- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/include/rapidjson/reader.h b/include/rapidjson/reader.h index 701255f..0615ce2 100644 --- a/include/rapidjson/reader.h +++ b/include/rapidjson/reader.h @@ -29,6 +29,9 @@ #include "internal/pow10.h" #include "internal/stack.h" +#include // strtod() +#include // HUGE_VAL + #if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) #include #pragma intrinsic(_BitScanForward) @@ -598,21 +601,27 @@ private: return codepoint; } + template class StackStream { public: - typedef typename TargetEncoding::Ch Ch; + typedef CharType Ch; StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} RAPIDJSON_FORCEINLINE void Put(Ch c) { *stack_.template Push() = c; ++length_; } - internal::Stack& stack_; - SizeType length_; + size_t Length() const { return length_; } + Ch* Pop() { + return stack_.template Pop(length_); + } private: StackStream(const StackStream&); StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; }; // Parse string and generate String event. Different code paths for kParseInsituFlag. @@ -631,10 +640,11 @@ private: RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); } else { - StackStream stackStream(stack_); + StackStream stackStream(stack_); ParseStringToStream(s, stackStream); RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; - if (!handler.String(stack_.template Pop(stackStream.length_), stackStream.length_ - 1, true)) + size_t length = stackStream.Length(); + if (!handler.String(stackStream.Pop(), length - 1, true)) RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); } } @@ -700,27 +710,17 @@ private: } } - inline double StrtodFastPath(double significand, int exp) { - // Fast path only works on limited range of values. - // But for simplicity and performance, currently only implement this. - // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ - if (exp < -308) - return 0.0; - else if (exp >= 0) - return significand * internal::Pow10(exp); - else - return significand / internal::Pow10(-exp); - } - template void ParseNumber(InputStream& is, Handler& handler) { internal::StreamLocalCopy copy(is); InputStream& s(copy.s); + StackStream stackStream(stack_); // Backup string for slow path double conversion. // Parse minus bool minus = false; if (s.Peek() == '-') { minus = true; + stackStream.Put(s.Peek()); s.Take(); } @@ -730,9 +730,11 @@ private: bool use64bit = false; if (s.Peek() == '0') { i = 0; + stackStream.Put(s.Peek()); s.Take(); } else if (s.Peek() >= '1' && s.Peek() <= '9') { + stackStream.Put(s.Peek()); i = static_cast(s.Take() - '0'); if (minus) @@ -744,6 +746,7 @@ private: break; } } + stackStream.Put(s.Peek()); i = i * 10 + static_cast(s.Take() - '0'); } else @@ -755,6 +758,7 @@ private: break; } } + stackStream.Put(s.Peek()); i = i * 10 + static_cast(s.Take() - '0'); } } @@ -762,71 +766,67 @@ private: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); // Parse 64bit int - double d = 0.0; bool useDouble = false; + bool useStrtod = false; if (use64bit) { if (minus) while (s.Peek() >= '0' && s.Peek() <= '9') { if (i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC)) // 2^63 = 9223372036854775808 if (i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8') { - d = (double)i64; useDouble = true; break; } + stackStream.Put(s.Peek()); i64 = i64 * 10 + static_cast(s.Take() - '0'); } else while (s.Peek() >= '0' && s.Peek() <= '9') { if (i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999)) // 2^64 - 1 = 18446744073709551615 if (i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5') { - d = (double)i64; useDouble = true; break; } + stackStream.Put(s.Peek()); i64 = i64 * 10 + static_cast(s.Take() - '0'); } } // Force double for big integer if (useDouble) { - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (d >= 1.7976931348623157e307) // DBL_MAX / 10.0 - RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); - d = d * 10 + (s.Take() - '0'); - } + while (s.Peek() >= '0' && s.Peek() <= '9') + stackStream.Put(s.Take()); + useStrtod = true; } // Parse frac = decimal-point 1*DIGIT int expFrac = 0; if (s.Peek() == '.') { + stackStream.Put(s.Peek()); s.Take(); -#if RAPIDJSON_64BIT - // Use i64 to store significand in 64-bit architecture if (!useDouble) { - if (!use64bit) + if (!use64bit) { i64 = i; + use64bit = true; + } while (s.Peek() >= '0' && s.Peek() <= '9') { - if (i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999)) + if (i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999)) { + useStrtod = true; break; + } else { + stackStream.Put(s.Peek()); i64 = i64 * 10 + static_cast(s.Take() - '0'); --expFrac; } } - - d = (double)i64; } -#else - // Use double to store significand in 32-bit architecture - if (!useDouble) - d = use64bit ? (double)i64 : (double)i; -#endif + useDouble = true; while (s.Peek() >= '0' && s.Peek() <= '9') { - d = d * 10 + (s.Take() - '0'); + stackStream.Put(s.Take()); --expFrac; } @@ -837,23 +837,24 @@ private: // Parse exp = e [ minus / plus ] 1*DIGIT int exp = 0; if (s.Peek() == 'e' || s.Peek() == 'E') { - if (!useDouble) { - d = use64bit ? (double)i64 : (double)i; - useDouble = true; - } + useDouble = true; + stackStream.Put(s.Peek()); s.Take(); bool expMinus = false; if (s.Peek() == '+') s.Take(); else if (s.Peek() == '-') { + stackStream.Put(s.Peek()); s.Take(); expMinus = true; } if (s.Peek() >= '0' && s.Peek() <= '9') { + stackStream.Put(s.Peek()); exp = s.Take() - '0'; while (s.Peek() >= '0' && s.Peek() <= '9') { + stackStream.Put(s.Peek()); exp = exp * 10 + (s.Take() - '0'); if (exp > 308 && !expMinus) // exp > 308 should be rare, so it should be checked first. RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); @@ -868,17 +869,36 @@ private: // Finish parsing, call event according to the type of number. bool cont = true; - if (useDouble) { - int expSum = exp + expFrac; - if (expSum < -308) { - // Prevent expSum < -308, making Pow10(expSum) = 0 - d = StrtodFastPath(d, exp); - d = StrtodFastPath(d, expFrac); - } - else - d = StrtodFastPath(d, expSum); - cont = handler.Double(minus ? -d : d); + // Pop stack no matter if it will be used or not. + stackStream.Put('\0'); + const char* str = stackStream.Pop(); + + if (useDouble) { + int p = exp + expFrac; + double d; + uint64_t significand = use64bit ? i64 : i; + + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (!useStrtod && p >= -22 && p <= 22 && significand <= RAPIDJSON_UINT64_C2(0x001FFFFF, 0xFFFFFFFF)) { + if (p >= 0) + d = significand * internal::Pow10(p); + else + d = significand / internal::Pow10(-p); + + if (minus) + d = -d; + } + else { + char* end = 0; + d = strtod(str, &end); + RAPIDJSON_ASSERT(*end == '\0'); // Should have consumed the whole string. + + if (d == HUGE_VAL || d == -HUGE_VAL) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); + } + cont = handler.Double(d); } else { if (use64bit) { diff --git a/test/unittest/readertest.cpp b/test/unittest/readertest.cpp index caacc66..7a28d8c 100644 --- a/test/unittest/readertest.cpp +++ b/test/unittest/readertest.cpp @@ -119,7 +119,9 @@ TEST(Reader, ParseNumberHandler) { Reader reader; \ reader.Parse(s, h); \ EXPECT_EQ(1u, h.step_); \ - EXPECT_DOUBLE_EQ(x, h.actual_); \ + EXPECT_EQ(x, h.actual_); \ + if (x != h.actual_) \ + printf(" Actual: %.17g\nExpected: %.17g\n", h.actual_, x);\ } TEST_NUMBER(ParseUintHandler, "0", 0);