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.
This commit is contained in:
miloyip 2014-09-03 01:02:38 +08:00
parent a46d152218
commit 0580d42d11
2 changed files with 74 additions and 52 deletions

View File

@ -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:
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:
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:
} }
} }
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:
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:
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:
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:
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:
// 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) { useDouble = true;
d = use64bit ? (double)i64 : (double)i; stackStream.Put(s.Peek());
useDouble = true;
}
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:
// 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;
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 { else {
if (use64bit) { if (use64bit) {

View File

@ -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);