Commit 0580d42d authored by miloyip's avatar miloyip

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.
parent a46d1522
...@@ -29,6 +29,9 @@ ...@@ -29,6 +29,9 @@
#include "internal/pow10.h" #include "internal/pow10.h"
#include "internal/stack.h" #include "internal/stack.h"
#include <cstdlib> // strtod()
#include <cmath> // HUGE_VAL
#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) #if defined(RAPIDJSON_SIMD) && defined(_MSC_VER)
#include <intrin.h> #include <intrin.h>
#pragma intrinsic(_BitScanForward) #pragma intrinsic(_BitScanForward)
...@@ -598,21 +601,27 @@ private: ...@@ -598,21 +601,27 @@ private:
return codepoint; return codepoint;
} }
template <typename CharType>
class StackStream { class StackStream {
public: public:
typedef typename TargetEncoding::Ch Ch; typedef CharType Ch;
StackStream(internal::Stack<StackAllocator>& stack) : stack_(stack), length_(0) {} StackStream(internal::Stack<StackAllocator>& stack) : stack_(stack), length_(0) {}
RAPIDJSON_FORCEINLINE void Put(Ch c) { RAPIDJSON_FORCEINLINE void Put(Ch c) {
*stack_.template Push<Ch>() = c; *stack_.template Push<Ch>() = c;
++length_; ++length_;
} }
internal::Stack<StackAllocator>& stack_; size_t Length() const { return length_; }
SizeType length_; Ch* Pop() {
return stack_.template Pop<Ch>(length_);
}
private: private:
StackStream(const StackStream&); StackStream(const StackStream&);
StackStream& operator=(const StackStream&); StackStream& operator=(const StackStream&);
internal::Stack<StackAllocator>& stack_;
SizeType length_;
}; };
// Parse string and generate String event. Different code paths for kParseInsituFlag. // Parse string and generate String event. Different code paths for kParseInsituFlag.
...@@ -631,10 +640,11 @@ private: ...@@ -631,10 +640,11 @@ private:
RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell());
} }
else { else {
StackStream stackStream(stack_); StackStream<typename TargetEncoding::Ch> stackStream(stack_);
ParseStringToStream<parseFlags, SourceEncoding, TargetEncoding>(s, stackStream); ParseStringToStream<parseFlags, SourceEncoding, TargetEncoding>(s, stackStream);
RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID;
if (!handler.String(stack_.template Pop<typename TargetEncoding::Ch>(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()); RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell());
} }
} }
...@@ -700,27 +710,17 @@ private: ...@@ -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<unsigned parseFlags, typename InputStream, typename Handler> template<unsigned parseFlags, typename InputStream, typename Handler>
void ParseNumber(InputStream& is, Handler& handler) { void ParseNumber(InputStream& is, Handler& handler) {
internal::StreamLocalCopy<InputStream> copy(is); internal::StreamLocalCopy<InputStream> copy(is);
InputStream& s(copy.s); InputStream& s(copy.s);
StackStream<char> stackStream(stack_); // Backup string for slow path double conversion.
// Parse minus // Parse minus
bool minus = false; bool minus = false;
if (s.Peek() == '-') { if (s.Peek() == '-') {
minus = true; minus = true;
stackStream.Put(s.Peek());
s.Take(); s.Take();
} }
...@@ -730,9 +730,11 @@ private: ...@@ -730,9 +730,11 @@ private:
bool use64bit = false; bool use64bit = false;
if (s.Peek() == '0') { if (s.Peek() == '0') {
i = 0; i = 0;
stackStream.Put(s.Peek());
s.Take(); s.Take();
} }
else if (s.Peek() >= '1' && s.Peek() <= '9') { else if (s.Peek() >= '1' && s.Peek() <= '9') {
stackStream.Put(s.Peek());
i = static_cast<unsigned>(s.Take() - '0'); i = static_cast<unsigned>(s.Take() - '0');
if (minus) if (minus)
...@@ -744,6 +746,7 @@ private: ...@@ -744,6 +746,7 @@ private:
break; break;
} }
} }
stackStream.Put(s.Peek());
i = i * 10 + static_cast<unsigned>(s.Take() - '0'); i = i * 10 + static_cast<unsigned>(s.Take() - '0');
} }
else else
...@@ -755,6 +758,7 @@ private: ...@@ -755,6 +758,7 @@ private:
break; break;
} }
} }
stackStream.Put(s.Peek());
i = i * 10 + static_cast<unsigned>(s.Take() - '0'); i = i * 10 + static_cast<unsigned>(s.Take() - '0');
} }
} }
...@@ -762,71 +766,67 @@ private: ...@@ -762,71 +766,67 @@ private:
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
// Parse 64bit int // Parse 64bit int
double d = 0.0;
bool useDouble = false; bool useDouble = false;
bool useStrtod = false;
if (use64bit) { if (use64bit) {
if (minus) if (minus)
while (s.Peek() >= '0' && s.Peek() <= '9') { while (s.Peek() >= '0' && s.Peek() <= '9') {
if (i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC)) // 2^63 = 9223372036854775808 if (i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC)) // 2^63 = 9223372036854775808
if (i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8') { if (i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8') {
d = (double)i64;
useDouble = true; useDouble = true;
break; break;
} }
stackStream.Put(s.Peek());
i64 = i64 * 10 + static_cast<unsigned>(s.Take() - '0'); i64 = i64 * 10 + static_cast<unsigned>(s.Take() - '0');
} }
else else
while (s.Peek() >= '0' && s.Peek() <= '9') { 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)) // 2^64 - 1 = 18446744073709551615
if (i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5') { if (i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5') {
d = (double)i64;
useDouble = true; useDouble = true;
break; break;
} }
stackStream.Put(s.Peek());
i64 = i64 * 10 + static_cast<unsigned>(s.Take() - '0'); i64 = i64 * 10 + static_cast<unsigned>(s.Take() - '0');
} }
} }
// Force double for big integer // Force double for big integer
if (useDouble) { if (useDouble) {
while (s.Peek() >= '0' && s.Peek() <= '9') { while (s.Peek() >= '0' && s.Peek() <= '9')
if (d >= 1.7976931348623157e307) // DBL_MAX / 10.0 stackStream.Put(s.Take());
RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); useStrtod = true;
d = d * 10 + (s.Take() - '0');
}
} }
// Parse frac = decimal-point 1*DIGIT // Parse frac = decimal-point 1*DIGIT
int expFrac = 0; int expFrac = 0;
if (s.Peek() == '.') { if (s.Peek() == '.') {
stackStream.Put(s.Peek());
s.Take(); s.Take();
#if RAPIDJSON_64BIT
// Use i64 to store significand in 64-bit architecture
if (!useDouble) { if (!useDouble) {
if (!use64bit) if (!use64bit) {
i64 = i; i64 = i;
use64bit = true;
}
while (s.Peek() >= '0' && s.Peek() <= '9') { while (s.Peek() >= '0' && s.Peek() <= '9') {
if (i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999)) if (i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999)) {
useStrtod = true;
break; break;
}
else { else {
stackStream.Put(s.Peek());
i64 = i64 * 10 + static_cast<unsigned>(s.Take() - '0'); i64 = i64 * 10 + static_cast<unsigned>(s.Take() - '0');
--expFrac; --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; useDouble = true;
while (s.Peek() >= '0' && s.Peek() <= '9') { while (s.Peek() >= '0' && s.Peek() <= '9') {
d = d * 10 + (s.Take() - '0'); stackStream.Put(s.Take());
--expFrac; --expFrac;
} }
...@@ -837,23 +837,24 @@ private: ...@@ -837,23 +837,24 @@ private:
// Parse exp = e [ minus / plus ] 1*DIGIT // Parse exp = e [ minus / plus ] 1*DIGIT
int exp = 0; int exp = 0;
if (s.Peek() == 'e' || s.Peek() == 'E') { 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(); s.Take();
bool expMinus = false; bool expMinus = false;
if (s.Peek() == '+') if (s.Peek() == '+')
s.Take(); s.Take();
else if (s.Peek() == '-') { else if (s.Peek() == '-') {
stackStream.Put(s.Peek());
s.Take(); s.Take();
expMinus = true; expMinus = true;
} }
if (s.Peek() >= '0' && s.Peek() <= '9') { if (s.Peek() >= '0' && s.Peek() <= '9') {
stackStream.Put(s.Peek());
exp = s.Take() - '0'; exp = s.Take() - '0';
while (s.Peek() >= '0' && s.Peek() <= '9') { while (s.Peek() >= '0' && s.Peek() <= '9') {
stackStream.Put(s.Peek());
exp = exp * 10 + (s.Take() - '0'); exp = exp * 10 + (s.Take() - '0');
if (exp > 308 && !expMinus) // exp > 308 should be rare, so it should be checked first. if (exp > 308 && !expMinus) // exp > 308 should be rare, so it should be checked first.
RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell()); RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell());
...@@ -868,17 +869,36 @@ private: ...@@ -868,17 +869,36 @@ private:
// Finish parsing, call event according to the type of number. // Finish parsing, call event according to the type of number.
bool cont = true; bool cont = true;
// Pop stack no matter if it will be used or not.
stackStream.Put('\0');
const char* str = stackStream.Pop();
if (useDouble) { if (useDouble) {
int expSum = exp + expFrac; int p = exp + expFrac;
if (expSum < -308) { double d;
// Prevent expSum < -308, making Pow10(expSum) = 0 uint64_t significand = use64bit ? i64 : i;
d = StrtodFastPath(d, exp);
d = StrtodFastPath(d, expFrac); // 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 else
d = StrtodFastPath(d, expSum); 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.
cont = handler.Double(minus ? -d : d); if (d == HUGE_VAL || d == -HUGE_VAL)
RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, s.Tell());
}
cont = handler.Double(d);
} }
else { else {
if (use64bit) { if (use64bit) {
......
...@@ -119,7 +119,9 @@ TEST(Reader, ParseNumberHandler) { ...@@ -119,7 +119,9 @@ TEST(Reader, ParseNumberHandler) {
Reader reader; \ Reader reader; \
reader.Parse(s, h); \ reader.Parse(s, h); \
EXPECT_EQ(1u, h.step_); \ 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); TEST_NUMBER(ParseUintHandler, "0", 0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment