Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse JSON number to double in full-precision. #137

Merged
merged 40 commits into from
Nov 30, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d8a51bf
Add test case
miloyip Aug 28, 2014
a46d152
Merge branch 'master' into issue120floatprecision
miloyip Aug 28, 2014
0580d42
Fallback strtod() when not able to do fast-path
miloyip Sep 2, 2014
818f6f1
Add random tests for ParseNumber
miloyip Sep 3, 2014
b043691
Check "fast path cases in disguise" in strtod
miloyip Sep 3, 2014
d875f16
Refactor ParseNumber for two modes (incomplete)
miloyip Sep 5, 2014
881c91d
Merge master and implement kParseFullPrecision
miloyip Sep 5, 2014
a71f2e6
Optimize ParseNumber()
miloyip Sep 5, 2014
c4a657d
Fix ParseNumber_Integer test
miloyip Sep 5, 2014
add0d9c
Compute error statistics of normal precision
miloyip Sep 5, 2014
86d63ff
Update document for kParseFullPrecisionFlag
miloyip Sep 6, 2014
774a4aa
Fix VC2010 which don't have std::isnan() et al.
miloyip Sep 10, 2014
30ea2a3
Prepare custom strtod data. (cannot pass unit test) [ci skip]
miloyip Sep 10, 2014
359ebc7
Extract conversion code to strtod.h [ci skip]
miloyip Sep 10, 2014
4bd240a
Implementing custom strtod, fail on some cases [ci skip]
miloyip Sep 12, 2014
98dd0a0
Make custom strtod work for denormal numbers and some boundary cases …
miloyip Sep 13, 2014
855da06
Makes gcc x64 runnable, but failed on one case. [ci skip]
miloyip Sep 14, 2014
4c21288
Add 32-bit support for custom strtod
miloyip Sep 14, 2014
fa52a26
Fix a unit test warning and suppress a failing case
miloyip Sep 14, 2014
cbd7475
Fix normal-subnormal boundary and add more boundary cases in unit tests.
miloyip Sep 14, 2014
bea4fa7
Remove unused BigInteger::operator+=(const BigInteger&)
miloyip Sep 14, 2014
b29acfb
Limit significand to 17 digits for fast path
miloyip Sep 15, 2014
50fc3fe
Fix round towards even
miloyip Sep 15, 2014
a425ad5
Trimming leading/trailing zeros and correct underflow case
miloyip Sep 16, 2014
4f99e25
Minor code cleaning
miloyip Sep 16, 2014
74b81fa
Extract classes into various files.
miloyip Sep 16, 2014
299e9f1
Added missing files
miloyip Sep 16, 2014
5171775
Minor optimizations in BigInteger
miloyip Sep 16, 2014
475b242
Minor refactoring before optimization trial
miloyip Sep 16, 2014
faa877f
Partial StrtodDiyFp implementation [ci skip]
miloyip Sep 19, 2014
b4e2d58
Temp commit
miloyip Oct 31, 2014
40852f4
Fixes StrtodDiyFp bugs
miloyip Nov 14, 2014
22ca931
Fix gcc/clang compilation errors and turn off exhaustive number test
miloyip Nov 14, 2014
57b9130
Merge remote-tracking branch 'origin/master' into issue120floatprecis…
miloyip Nov 14, 2014
3679c28
Merge remote-tracking branch 'origin/master' into issue120floatprecis…
miloyip Nov 23, 2014
b855c3f
Minor optimization of strtod
miloyip Nov 23, 2014
0a17e1a
Fix namespace compilation errors
miloyip Nov 23, 2014
23b7a5e
Add RAPIDJSON_PARSE_DEFAULT_FLAGS for customizing kParseDefaultFlags
miloyip Nov 30, 2014
92554b5
Merge remote-tracking branch 'origin/master' into issue120floatprecision
miloyip Nov 30, 2014
e62d537
Merge remote-tracking branch 'origin/master' into issue120floatprecision
miloyip Nov 30, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ Parse flags | Meaning
`kParseDefaultFlags = 0` | Default parse flags.
`kParseInsituFlag` | In-situ(destructive) parsing.
`kParseValidateEncodingFlag` | Validate encoding of JSON strings.
`kParseIterativeFlag` | Iterative(constant complexity in terms of function call stack size) parsing.
`kParseStopWhenDoneFlag` | After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate `kParseErrorDocumentRootNotSingular` error. Using this flag for parsing multiple JSONs in the same stream.
`kParseFullPrecisionFlag` | Parse number in full precision (slower). If this flag is not set, the normal precision (faster) is used. Normal precision has maximum 3 [ULP](http://en.wikipedia.org/wiki/Unit_in_the_last_place) error.

By using a non-type template parameter, instead of a function parameter, C++ compiler can generate code which is optimized for specified combinations, improving speed, and reducing code size (if only using a single specialization). The downside is the flags needed to be determined in compile-time.

Expand Down
290 changes: 290 additions & 0 deletions include/rapidjson/internal/biginteger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
// Copyright (C) 2011 Milo Yip
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef RAPIDJSON_BIGINTEGER_H_
#define RAPIDJSON_BIGINTEGER_H_

#include "../rapidjson.h"

RAPIDJSON_NAMESPACE_BEGIN
namespace internal {

class BigInteger {
public:
typedef uint64_t Type;

BigInteger(const BigInteger& rhs) : count_(rhs.count_) {
std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type));
}

explicit BigInteger(uint64_t u) : count_(1) {
digits_[0] = u;
}

BigInteger(const char* decimals, size_t length) : count_(1) {
RAPIDJSON_ASSERT(length > 0);
digits_[0] = 0;
size_t i = 0;
const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19
while (length >= kMaxDigitPerIteration) {
AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration);
length -= kMaxDigitPerIteration;
i += kMaxDigitPerIteration;
}

if (length > 0)
AppendDecimal64(decimals + i, decimals + i + length);
}

BigInteger& operator=(uint64_t u) {
digits_[0] = u;
count_ = 1;
return *this;
}

BigInteger& operator+=(uint64_t u) {
Type backup = digits_[0];
digits_[0] += u;
for (size_t i = 0; i < count_ - 1; i++) {
if (digits_[i] >= backup)
return *this; // no carry
backup = digits_[i + 1];
digits_[i + 1] += 1;
}

// Last carry
if (digits_[count_ - 1] < backup)
PushBack(1);

return *this;
}

BigInteger& operator*=(uint64_t u) {
if (u == 0) return *this = 0;
if (u == 1) return *this;
if (*this == 1) return *this = u;

uint64_t k = 0;
for (size_t i = 0; i < count_; i++) {
uint64_t hi;
digits_[i] = MulAdd64(digits_[i], u, k, &hi);
k = hi;
}

if (k > 0)
PushBack(k);

return *this;
}

BigInteger& operator*=(uint32_t u) {
if (u == 0) return *this = 0;
if (u == 1) return *this;
if (*this == 1) return *this = u;

uint32_t k = 0;
for (size_t i = 0; i < count_; i++) {
const uint64_t c = digits_[i] >> 32;
const uint64_t d = digits_[i] & 0xFFFFFFFF;
const uint64_t uc = u * c;
const uint64_t ud = u * d;
const uint64_t p0 = ud + k;
const uint64_t p1 = uc + (p0 >> 32);
digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32);
k = p1 >> 32;
}

if (k > 0)
PushBack(k);

return *this;
}

BigInteger& operator<<=(size_t shift) {
if (IsZero() || shift == 0) return *this;

size_t offset = shift / kTypeBit;
size_t interShift = shift % kTypeBit;
RAPIDJSON_ASSERT(count_ + offset <= kCapacity);

if (interShift == 0) {
std::memmove(&digits_[count_ - 1 + offset], &digits_[count_ - 1], count_ * sizeof(Type));
count_ += offset;
}
else {
digits_[count_] = 0;
for (size_t i = count_; i > 0; i--)
digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift));
digits_[offset] = digits_[0] << interShift;
count_ += offset;
if (digits_[count_])
count_++;
}

std::memset(digits_, 0, offset * sizeof(Type));

return *this;
}

bool operator==(const BigInteger& rhs) const {
return count_ == rhs.count_ && memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0;
}

bool operator==(const Type rhs) const {
return count_ == 1 && digits_[0] == rhs;
}

BigInteger& MultiplyPow5(unsigned exp) {
static const uint32_t kPow5[12] = {
5,
5 * 5,
5 * 5 * 5,
5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5
};
if (exp == 0) return *this;
for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27
for (; exp >= 13; exp -= 13) *this *= 1220703125u; // 5^13
if (exp > 0) *this *= kPow5[exp - 1];
return *this;
}

// Compute absolute difference of this and rhs.
// Return false if this < rhs
bool Difference(const BigInteger& rhs, BigInteger* out) const {
int cmp = Compare(rhs);
if (cmp == 0) {
*out = BigInteger(0);
return false;
}
const BigInteger *a, *b; // Makes a > b
bool ret;
if (cmp < 0) { a = &rhs; b = this; ret = true; }
else { a = this; b = &rhs; ret = false; }

Type borrow = 0;
for (size_t i = 0; i < a->count_; i++) {
Type d = a->digits_[i] - borrow;
if (i < b->count_)
d -= b->digits_[i];
borrow = (d > a->digits_[i]) ? 1 : 0;
out->digits_[i] = d;
if (d != 0)
out->count_ = i + 1;
}

return ret;
}

int Compare(const BigInteger& rhs) const {
if (count_ != rhs.count_)
return count_ < rhs.count_ ? -1 : 1;

for (size_t i = count_; i-- > 0;)
if (digits_[i] != rhs.digits_[i])
return digits_[i] < rhs.digits_[i] ? -1 : 1;

return 0;
}

size_t GetCount() const { return count_; }
Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; }
bool IsZero() const { return count_ == 1 && digits_[0] == 0; }

private:
void AppendDecimal64(const char* begin, const char* end) {
uint64_t u = ParseUint64(begin, end);
if (IsZero())
*this = u;
else {
unsigned exp = static_cast<unsigned>(end - begin);
(MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u
}
}

void PushBack(Type digit) {
RAPIDJSON_ASSERT(count_ < kCapacity);
digits_[count_++] = digit;
}

static uint64_t ParseUint64(const char* begin, const char* end) {
uint64_t r = 0;
for (const char* p = begin; p != end; ++p) {
RAPIDJSON_ASSERT(*p >= '0' && *p <= '9');
r = r * 10 + (*p - '0');
}
return r;
}

// Assume a * b + k < 2^128
static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) {
#if defined(_MSC_VER) && defined(_M_AMD64)
uint64_t low = _umul128(a, b, outHigh) + k;
if (low < k)
(*outHigh)++;
return low;
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__)
unsigned __int128 p = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
p += k;
*outHigh = p >> 64;
return static_cast<uint64_t>(p);
#else
const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32;
uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1;
x1 += (x0 >> 32); // can't give carry
x1 += x2;
if (x1 < x2)
x3 += (static_cast<uint64_t>(1) << 32);
uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF);
uint64_t hi = x3 + (x1 >> 32);

lo += k;
if (lo < k)
hi++;
*outHigh = hi;
return lo;
#endif
}

static Type FullAdd(Type a, Type b, bool inCarry, bool* outCarry) {
Type c = a + b + (inCarry ? 1 : 0);
*outCarry = c < a;
return c;
}

static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000
static const size_t kCapacity = kBitCount / sizeof(Type);
static const size_t kTypeBit = sizeof(Type) * 8;

Type digits_[kCapacity];
size_t count_;
};

} // namespace internal
RAPIDJSON_NAMESPACE_END

#endif // RAPIDJSON_BIGINTEGER_H_
Loading