From 8cd16c6f08d93c0763ec715a84a110b780855152 Mon Sep 17 00:00:00 2001 From: jealouscloud Date: Sun, 20 May 2018 14:53:35 -0400 Subject: [PATCH] Implement google's double-conversion fallbacks * Replaces the need for a call to .net's double.Parse/ToString * Slightly improves performance * Ensures binary equivalence with javascript based json --- .../Internal/DoubleConversion/Bignum.cs | 818 ++++++++++++++++++ .../Internal/DoubleConversion/Bignumdtoa.cs | 667 ++++++++++++++ .../DoubleToStringConverter.cs | 62 +- .../Internal/DoubleConversion/IEEE.cs | 5 + .../DoubleConversion/StringToDouble.cs | 189 +++- .../StringToDoubleConverter.cs | 25 +- 6 files changed, 1682 insertions(+), 84 deletions(-) create mode 100755 src/Utf8Json/Internal/DoubleConversion/Bignum.cs create mode 100755 src/Utf8Json/Internal/DoubleConversion/Bignumdtoa.cs mode change 100644 => 100755 src/Utf8Json/Internal/DoubleConversion/DoubleToStringConverter.cs mode change 100644 => 100755 src/Utf8Json/Internal/DoubleConversion/IEEE.cs mode change 100644 => 100755 src/Utf8Json/Internal/DoubleConversion/StringToDouble.cs mode change 100644 => 100755 src/Utf8Json/Internal/DoubleConversion/StringToDoubleConverter.cs diff --git a/src/Utf8Json/Internal/DoubleConversion/Bignum.cs b/src/Utf8Json/Internal/DoubleConversion/Bignum.cs new file mode 100755 index 00000000..404fea8d --- /dev/null +++ b/src/Utf8Json/Internal/DoubleConversion/Bignum.cs @@ -0,0 +1,818 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Chunk = System.UInt32; +using DoubleChunk = System.UInt64; +namespace Utf8Json.Internal.DoubleConversion +{ + internal class Bignum + { + // 3584 = 128 * 28. We can represent 2^3584 > 10^1000 accurately. + // This bignum can encode much bigger numbers, since it contains an + // exponent. + public const int kMaxSignificantBits = 3584; + const int kChunkSize = sizeof(Chunk) * 8; + const int kDoubleChunkSize = sizeof(DoubleChunk) * 8; + // With bigit size of 28 we loose some bits, but a double still fits easily + // into two chunks, and more importantly we can use the Comba multiplication. + const int kBigitSize = 28; + const Chunk kBigitMask = (1 << kBigitSize) - 1; + // Every instance allocates kBigitLength chunks on the stack. Bignums cannot + // grow. There are no checks if the stack-allocated space is sufficient. + const int kBigitCapacity = kMaxSignificantBits / kBigitSize; + private Chunk[] bigits_ = new Chunk[kBigitCapacity]; + + // Chunk bigits_buffer_[kBigitCapacity]; + // A vector backed by bigits_buffer_. This way accesses to the array are + // checked for out-of-bounds errors. + // Vector bigits_; + int used_digits_; + int exponent_; + + + public Bignum() + { + used_digits_ = 0; + exponent_ = 0; + for (int i = 0; i < kBigitCapacity; ++i) + { + bigits_[i] = 0; + } + } + + + public void Times10() + { + MultiplyByUInt32(10); + } + + + public void AssignUInt16(UInt16 value) + { + Zero(); + if (value == 0) return; + + EnsureCapacity(1); + + bigits_[0] = value; + used_digits_ = 1; + } + + + public void AssignUInt64(UInt64 value) + { + unchecked + { + const int kUInt64Size = 64; + + Zero(); + if (value == 0) return; + + int needed_bigits = kUInt64Size / kBigitSize + 1; + EnsureCapacity(needed_bigits); + for (int i = 0; i < needed_bigits; ++i) + { + bigits_[i] = (Chunk)(value & kBigitMask); + value = value >> kBigitSize; + } + used_digits_ = needed_bigits; + Clamp(); + } + } + + + public void AssignBignum(Bignum other) + { + exponent_ = other.exponent_; + for (int i = 0; i < other.used_digits_; ++i) + { + bigits_[i] = other.bigits_[i]; + } + // Clear the excess digits (if there were any). + for (int i = other.used_digits_; i < used_digits_; ++i) + { + bigits_[i] = 0; + } + used_digits_ = other.used_digits_; + } + + + public static UInt64 ReadUInt64(Vector buffer, + int from, + int digits_to_read) + { + unchecked + { + UInt64 result = 0; + for (int i = from; i < from + digits_to_read; ++i) + { + int digit = buffer[i] - '0'; + // ASSERT(0 <= digit && digit <= 9); + result = result * 10 + (UInt64)digit; + } + return result; + } + } + + + public void AssignDecimalString(Vector value) + { + unchecked + { + // 2^64 = 18446744073709551616 > 10^19 + const int kMaxUint64DecimalDigits = 19; + Zero(); + int length = value.length(); + uint pos = 0; + // Let's just say that each digit needs 4 bits. + while (length >= kMaxUint64DecimalDigits) + { + UInt64 _digits = ReadUInt64(value, (int)pos, kMaxUint64DecimalDigits); + pos += kMaxUint64DecimalDigits; + length -= kMaxUint64DecimalDigits; + MultiplyByPowerOfTen(kMaxUint64DecimalDigits); + AddUInt64(_digits); + } + UInt64 digits = ReadUInt64(value, (int)pos, length); + MultiplyByPowerOfTen(length); + AddUInt64(digits); + Clamp(); + } + } + + + public void AddUInt64(UInt64 operand) + { + if (operand == 0) return; + Bignum other = new Bignum(); + other.AssignUInt64(operand); + AddBignum(other); + } + + + public void AddBignum(Bignum other) + { + unchecked + { + // ASSERT(IsClamped()); + // ASSERT(other.IsClamped()); + // If this has a greater exponent than other append zero-bigits to this. + // After this call exponent_ <= other.exponent_. + Align(other); + + // There are two possibilities: + // aaaaaaaaaaa 0000 (where the 0s represent a's exponent) + // bbbbb 00000000 + // ---------------- + // ccccccccccc 0000 + // or + // aaaaaaaaaa 0000 + // bbbbbbbbb 0000000 + // ----------------- + // cccccccccccc 0000 + // In both cases we might need a carry bigit. + + EnsureCapacity(1 + Math.Max(BigitLength(), other.BigitLength()) - exponent_); + Chunk carry = 0; + int bigit_pos = other.exponent_ - exponent_; + // ASSERT(bigit_pos >= 0); + for (int i = 0; i < other.used_digits_; ++i) + { + Chunk sum = bigits_[bigit_pos] + other.bigits_[i] + carry; + bigits_[bigit_pos] = sum & kBigitMask; + carry = sum >> kBigitSize; + bigit_pos++; + } + + while (carry != 0) + { + Chunk sum = bigits_[bigit_pos] + carry; + bigits_[bigit_pos] = sum & kBigitMask; + carry = sum >> kBigitSize; + bigit_pos++; + } + used_digits_ = Math.Max(bigit_pos, used_digits_); + // ASSERT(IsClamped()); + } + } + + + public void SubtractBignum(Bignum other) + { + unchecked + { + // ASSERT(IsClamped()); + // ASSERT(other.IsClamped()); + // We require this to be bigger than other. + // ASSERT(LessEqual(other, *this)); + + Align(other); + + int offset = other.exponent_ - exponent_; + Chunk borrow = 0; + int i; + for (i = 0; i < other.used_digits_; ++i) + { + // ASSERT((borrow == 0) || (borrow == 1)); + Chunk difference = bigits_[i + offset] - other.bigits_[i] - borrow; + bigits_[i + offset] = difference & kBigitMask; + borrow = difference >> (kChunkSize - 1); + } + while (borrow != 0) + { + Chunk difference = bigits_[i + offset] - borrow; + bigits_[i + offset] = difference & kBigitMask; + borrow = difference >> (kChunkSize - 1); + ++i; + } + Clamp(); + } + } + + + public void ShiftLeft(int shift_amount) + { + if (used_digits_ == 0) return; + exponent_ += shift_amount / kBigitSize; + int local_shift = shift_amount % kBigitSize; + EnsureCapacity(used_digits_ + 1); + BigitsShiftLeft(local_shift); + } + + + public void MultiplyByUInt32(UInt32 factor) + { + unchecked + { + if (factor == 1) return; + if (factor == 0) + { + Zero(); + return; + } + if (used_digits_ == 0) return; + + // The product of a bigit with the factor is of size kBigitSize + 32. + // Assert that this number + 1 (for the carry) fits into double chunk. + // ASSERT(kDoubleChunkSize >= kBigitSize + 32 + 1); + DoubleChunk carry = 0; + for (int i = 0; i < used_digits_; ++i) + { + DoubleChunk product = (DoubleChunk)factor * bigits_[i] + carry; + bigits_[i] = (Chunk)(product & kBigitMask); + carry = (product >> kBigitSize); + } + while (carry != 0) + { + EnsureCapacity(used_digits_ + 1); + bigits_[used_digits_] = (Chunk)(carry & kBigitMask); + used_digits_++; + carry >>= kBigitSize; + } + } + } + + + public void MultiplyByUInt64(UInt64 factor) + { + unchecked + { + if (factor == 1) return; + if (factor == 0) + { + Zero(); + return; + } + // ASSERT(kBigitSize < 32); + UInt64 carry = 0; + UInt64 low = factor & 0xFFFFFFFF; + UInt64 high = factor >> 32; + for (int i = 0; i < used_digits_; ++i) + { + UInt64 product_low = low * bigits_[i]; + UInt64 product_high = high * bigits_[i]; + UInt64 tmp = (carry & kBigitMask) + product_low; + bigits_[i] = (Chunk)(tmp & kBigitMask); + carry = (carry >> kBigitSize) + (tmp >> kBigitSize) + + (product_high << (32 - kBigitSize)); + } + while (carry != 0) + { + EnsureCapacity(used_digits_ + 1); + bigits_[used_digits_] = (Chunk)(carry & kBigitMask); + used_digits_++; + carry >>= kBigitSize; + } + } + } + const UInt64 kFive27 = 0x6765c793fa10079d; + const UInt16 kFive1 = 5; + const UInt16 kFive2 = kFive1 * 5; + const UInt16 kFive3 = kFive2 * 5; + const UInt16 kFive4 = kFive3 * 5; + const UInt16 kFive5 = kFive4 * 5; + const UInt16 kFive6 = kFive5 * 5; + const UInt32 kFive7 = kFive6 * 5; + const UInt32 kFive8 = kFive7 * 5; + const UInt32 kFive9 = kFive8 * 5; + const UInt32 kFive10 = kFive9 * 5; + const UInt32 kFive11 = kFive10 * 5; + const UInt32 kFive12 = kFive11 * 5; + const UInt32 kFive13 = kFive12 * 5; + readonly UInt32[] kFive1_to_12 = + { kFive1, kFive2, kFive3, kFive4, kFive5, kFive6, + kFive7, kFive8, kFive9, kFive10, kFive11, kFive12 }; + public void MultiplyByPowerOfTen(int exponent) + { + // ASSERT(exponent >= 0); + if (exponent == 0) return; + if (used_digits_ == 0) return; + + // We shift by exponent at the end just before returning. + int remaining_exponent = exponent; + while (remaining_exponent >= 27) + { + MultiplyByUInt64(kFive27); + remaining_exponent -= 27; + } + while (remaining_exponent >= 13) + { + MultiplyByUInt32(kFive13); + remaining_exponent -= 13; + } + if (remaining_exponent > 0) + { + MultiplyByUInt32(kFive1_to_12[remaining_exponent - 1]); + } + ShiftLeft(exponent); + } + + + public void Square() + { + unchecked + { + // ASSERT(IsClamped()); + int product_length = 2 * used_digits_; + EnsureCapacity(product_length); + + // Comba multiplication: compute each column separately. + // Example: r = a2a1a0 * b2b1b0. + // r = 1 * a0b0 + + // 10 * (a1b0 + a0b1) + + // 100 * (a2b0 + a1b1 + a0b2) + + // 1000 * (a2b1 + a1b2) + + // 10000 * a2b2 + // + // In the worst case we have to accumulate nb-digits products of digit*digit. + // + // Assert that the additional number of bits in a DoubleChunk are enough to + // sum up used_digits of Bigit*Bigit. + if ((1 << (2 * (kChunkSize - kBigitSize))) <= used_digits_) + { + // UNIMPLEMENTED(); + throw new Exception("UNIMPLEMENTED"); + } + DoubleChunk accumulator = 0; + // First shift the digits so we don't overwrite them. + int copy_offset = used_digits_; + for (int i = 0; i < used_digits_; ++i) + { + bigits_[copy_offset + i] = bigits_[i]; + } + // We have two loops to avoid some 'if's in the loop. + for (int i = 0; i < used_digits_; ++i) + { + // Process temporary digit i with power i. + // The sum of the two indices must be equal to i. + int bigit_index1 = i; + int bigit_index2 = 0; + // Sum all of the sub-products. + while (bigit_index1 >= 0) + { + Chunk chunk1 = bigits_[copy_offset + bigit_index1]; + Chunk chunk2 = bigits_[copy_offset + bigit_index2]; + accumulator += ((DoubleChunk)chunk1) * chunk2; + bigit_index1--; + bigit_index2++; + } + bigits_[i] = ((Chunk)accumulator) & kBigitMask; + accumulator >>= kBigitSize; + } + for (int i = used_digits_; i < product_length; ++i) + { + int bigit_index1 = used_digits_ - 1; + int bigit_index2 = i - bigit_index1; + // Invariant: sum of both indices is again equal to i. + // Inner loop runs 0 times on last iteration, emptying accumulator. + while (bigit_index2 < used_digits_) + { + Chunk chunk1 = bigits_[copy_offset + bigit_index1]; + Chunk chunk2 = bigits_[copy_offset + bigit_index2]; + accumulator += ((DoubleChunk)chunk1) * chunk2; + bigit_index1--; + bigit_index2++; + } + // The overwritten bigits_[i] will never be read in further loop iterations, + // because bigit_index1 and bigit_index2 are always greater + // than i - used_digits_. + bigits_[i] = ((Chunk)accumulator) & kBigitMask; + accumulator >>= kBigitSize; + } + // Since the result was guaranteed to lie inside the number the + // accumulator must be 0 now. + // ASSERT(accumulator == 0); + + // Don't forget to update the used_digits and the exponent. + used_digits_ = product_length; + exponent_ *= 2; + Clamp(); + } + } + + + public void AssignPowerUInt16(UInt16 _base, int power_exponent) + { + unchecked + { + // ASSERT(base != 0); + // ASSERT(power_exponent >= 0); + if (power_exponent == 0) + { + AssignUInt16(1); + return; + } + Zero(); + int shifts = 0; + // We expect base to be in range 2-32, and most often to be 10. + // It does not make much sense to implement different algorithms for counting + // the bits. + while ((_base & 1) == 0) + { + _base >>= 1; + shifts++; + } + int bit_size = 0; + int tmp_base = _base; + while (tmp_base != 0) + { + tmp_base >>= 1; + bit_size++; + } + int final_size = bit_size * power_exponent; + // 1 extra bigit for the shifting, and one for rounded final_size. + EnsureCapacity(final_size / kBigitSize + 2); + + // Left to Right exponentiation. + int mask = 1; + while (power_exponent >= mask) mask <<= 1; + + // The mask is now pointing to the bit above the most significant 1-bit of + // power_exponent. + // Get rid of first 1-bit; + mask >>= 2; + UInt64 this_value = _base; + + bool delayed_multiplication = false; + const UInt64 max_32bits = 0xFFFFFFFF; + while (mask != 0 && this_value <= max_32bits) + { + this_value = this_value * this_value; + // Verify that there is enough space in this_value to perform the + // multiplication. The first bit_size bits must be 0. + if ((power_exponent & mask) != 0) + { + // ASSERT(bit_size > 0); + UInt64 base_bits_mask = + ~((((UInt64)1) << (64 - bit_size)) - 1); + bool high_bits_zero = (this_value & base_bits_mask) == 0; + if (high_bits_zero) + { + this_value *= _base; + } + else + { + delayed_multiplication = true; + } + } + mask >>= 1; + } + AssignUInt64(this_value); + if (delayed_multiplication) + { + MultiplyByUInt32(_base); + } + + // Now do the same thing as a bignum. + while (mask != 0) + { + Square(); + if ((power_exponent & mask) != 0) + { + MultiplyByUInt32(_base); + } + mask >>= 1; + } + + // And finally add the saved shifts. + ShiftLeft(shifts * power_exponent); + } + } + + // Precondition: this/other < 16bit. + public UInt16 DivideModuloIntBignum(Bignum other) + { + unchecked + { + // ASSERT(IsClamped()); + // ASSERT(other.IsClamped()); + // ASSERT(other.used_digits_ > 0); + + // Easy case: if we have less digits than the divisor than the result is 0. + // Note: this handles the case where this == 0, too. + if (BigitLength() < other.BigitLength()) + { + return 0; + } + + Align(other); + + UInt16 result = 0; + // Start by removing multiples of 'other' until both numbers have the same + // number of digits. + while (BigitLength() > other.BigitLength()) + { + // This naive approach is extremely inefficient if `this` divided by other + // is big. This function is implemented for doubleToString where + // the result should be small (less than 10). + // ASSERT(other.bigits_[other.used_digits_ - 1] >= ((1 << kBigitSize) / 16)); + // ASSERT(bigits_[used_digits_ - 1] < 0x10000); + // Remove the multiples of the first digit. + // Example this = 23 and other equals 9. -> Remove 2 multiples. + result += (UInt16)bigits_[used_digits_ - 1]; + SubtractTimes(other, (int)(bigits_[used_digits_ - 1])); + } + + // ASSERT(BigitLength() == other.BigitLength()); + + // Both bignums are at the same length now. + // Since other has more than 0 digits we know that the access to + // bigits_[used_digits_ - 1] is safe. + Chunk this_bigit = bigits_[used_digits_ - 1]; + Chunk other_bigit = other.bigits_[other.used_digits_ - 1]; + + if (other.used_digits_ == 1) + { + // Shortcut for easy (and common) case. + int quotient = (int)(this_bigit / other_bigit); + bigits_[used_digits_ - 1] = (Chunk)(this_bigit - other_bigit * quotient); + // ASSERT(quotient < 0x10000); + result += (UInt16)quotient; + Clamp(); + return result; + } + + int division_estimate = (int)(this_bigit / (other_bigit + 1)); + // ASSERT(division_estimate < 0x10000); + result += (UInt16)division_estimate; + SubtractTimes(other, division_estimate); + + if (other_bigit * (division_estimate + 1) > this_bigit) + { + // No need to even try to subtract. Even if other's remaining digits were 0 + // another subtraction would be too much. + return result; + } + + while (LessEqual(other, this)) + { + SubtractBignum(other); + result++; + } + return result; + } + } + + + public Chunk BigitAt(int index) + { + if (index >= BigitLength()) return 0; + if (index < exponent_) return 0; + return bigits_[index - exponent_]; + } + + + public static int Compare(Bignum a, Bignum b) + { + // ASSERT(a.IsClamped()); + // ASSERT(b.IsClamped()); + int bigit_length_a = a.BigitLength(); + int bigit_length_b = b.BigitLength(); + if (bigit_length_a < bigit_length_b) return -1; + if (bigit_length_a > bigit_length_b) return +1; + for (int i = bigit_length_a - 1; i >= Math.Min(a.exponent_, b.exponent_); --i) + { + Chunk bigit_a = a.BigitAt(i); + Chunk bigit_b = b.BigitAt(i); + if (bigit_a < bigit_b) return -1; + if (bigit_a > bigit_b) return +1; + // Otherwise they are equal up to this digit. Try the next digit. + } + return 0; + } + + + public static int PlusCompare(Bignum a, Bignum b, Bignum c) + { + unchecked + { + // ASSERT(a.IsClamped()); + // ASSERT(b.IsClamped()); + // ASSERT(c.IsClamped()); + if (a.BigitLength() < b.BigitLength()) + { + return PlusCompare(b, a, c); + } + if (a.BigitLength() + 1 < c.BigitLength()) return -1; + if (a.BigitLength() > c.BigitLength()) return +1; + // The exponent encodes 0-bigits. So if there are more 0-digits in 'a' than + // 'b' has digits, then the bigit-length of 'a'+'b' must be equal to the one + // of 'a'. + if (a.exponent_ >= b.BigitLength() && a.BigitLength() < c.BigitLength()) + { + return -1; + } + + Chunk borrow = 0; + // Starting at min_exponent all digits are == 0. So no need to compare them. + int min_exponent = Math.Min(Math.Min(a.exponent_, b.exponent_), c.exponent_); + for (int i = c.BigitLength() - 1; i >= min_exponent; --i) + { + Chunk chunk_a = a.BigitAt(i); + Chunk chunk_b = b.BigitAt(i); + Chunk chunk_c = c.BigitAt(i); + Chunk sum = chunk_a + chunk_b; + if (sum > chunk_c + borrow) + { + return +1; + } + else + { + borrow = chunk_c + borrow - sum; + if (borrow > 1) return -1; + borrow <<= kBigitSize; + } + } + if (borrow == 0) return 0; + return -1; + } + } + + + void Clamp() + { + while (used_digits_ > 0 && bigits_[used_digits_ - 1] == 0) + { + used_digits_--; + } + if (used_digits_ == 0) + { + // Zero. + exponent_ = 0; + } + } + + + bool IsClamped() + { + return used_digits_ == 0 || bigits_[used_digits_ - 1] != 0; + } + + + void Zero() + { + for (int i = 0; i < used_digits_; ++i) + { + bigits_[i] = 0; + } + used_digits_ = 0; + exponent_ = 0; + } + void Align(Bignum other) + { + if (exponent_ > other.exponent_) + { + // If "X" represents a "hidden" digit (by the exponent) then we are in the + // following case (a == this, b == other): + // a: aaaaaaXXXX or a: aaaaaXXX + // b: bbbbbbX b: bbbbbbbbXX + // We replace some of the hidden digits (X) of a with 0 digits. + // a: aaaaaa000X or a: aaaaa0XX + int zero_digits = exponent_ - other.exponent_; + EnsureCapacity(used_digits_ + zero_digits); + for (int i = used_digits_ - 1; i >= 0; --i) + { + bigits_[i + zero_digits] = bigits_[i]; + } + for (int i = 0; i < zero_digits; ++i) + { + bigits_[i] = 0; + } + used_digits_ += zero_digits; + exponent_ -= zero_digits; + // ASSERT(used_digits_ >= 0); + // ASSERT(exponent_ >= 0); + } + } + + void BigitsShiftLeft(int shift_amount) + { + unchecked + { + Chunk carry = 0; + for (int i = 0; i < used_digits_; ++i) + { + Chunk new_carry = bigits_[i] >> (kBigitSize - shift_amount); + bigits_[i] = ((bigits_[i] << shift_amount) + carry) & kBigitMask; + carry = new_carry; + } + if (carry != 0) + { + bigits_[used_digits_] = carry; + used_digits_++; + } + } + } + void SubtractTimes(Bignum other, int factor) + { + unchecked + { + // ASSERT(exponent_ <= other.exponent_); + if (factor < 3) + { + for (int i = 0; i < factor; ++i) + { + SubtractBignum(other); + } + return; + } + Chunk borrow = 0; + int exponent_diff = other.exponent_ - exponent_; + for (int i = 0; i < other.used_digits_; ++i) + { + DoubleChunk product = ((DoubleChunk)factor) * other.bigits_[i]; + DoubleChunk remove = borrow + product; + Chunk difference = (Chunk)(bigits_[i + exponent_diff] - (remove & kBigitMask)); + bigits_[i + exponent_diff] = difference & kBigitMask; + borrow = (Chunk)((difference >> (kChunkSize - 1)) + + (remove >> kBigitSize)); + } + for (int i = other.used_digits_ + exponent_diff; i < used_digits_; ++i) + { + if (borrow == 0) return; + Chunk difference = bigits_[i] - borrow; + bigits_[i] = difference & kBigitMask; + borrow = difference >> (kChunkSize - 1); + } + Clamp(); + } + } + + void EnsureCapacity(int size) + { + if (size > kBigitCapacity) + { + //UNREACHABLE(); + throw new Exception("UNREACHABLE"); + } + } + private int BigitLength() { return used_digits_ + exponent_; } + public static bool Equal(Bignum a, Bignum b) + { + return Compare(a, b) == 0; + } + public static bool LessEqual(Bignum a, Bignum b) + { + return Compare(a, b) <= 0; + } + public static bool Less(Bignum a, Bignum b) + { + return Compare(a, b) < 0; + } + // Returns a + b == c + public static bool PlusEqual(Bignum a, Bignum b, Bignum c) + { + return PlusCompare(a, b, c) == 0; + } + // Returns a + b <= c + public static bool PlusLessEqual(Bignum a, Bignum b, Bignum c) + { + return PlusCompare(a, b, c) <= 0; + } + // Returns a + b < c + public static bool PlusLess(Bignum a, Bignum b, Bignum c) + { + return PlusCompare(a, b, c) < 0; + } + } +} \ No newline at end of file diff --git a/src/Utf8Json/Internal/DoubleConversion/Bignumdtoa.cs b/src/Utf8Json/Internal/DoubleConversion/Bignumdtoa.cs new file mode 100755 index 00000000..289595ae --- /dev/null +++ b/src/Utf8Json/Internal/DoubleConversion/Bignumdtoa.cs @@ -0,0 +1,667 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Chunk = System.UInt32; +using DoubleChunk = System.UInt64; +namespace Utf8Json.Internal.DoubleConversion +{ + enum BignumDtoaMode + { + // Return the shortest correct representation. + // For example the output of 0.299999999999999988897 is (the less accurate but + // correct) 0.3. + BIGNUM_DTOA_SHORTEST, + // Same as BIGNUM_DTOA_SHORTEST but for single-precision floats. + BIGNUM_DTOA_SHORTEST_SINGLE, + // Return a fixed number of digits after the decimal point. + // For instance fixed(0.1, 4) becomes 0.1000 + // If the input number is big, the output will be big. + BIGNUM_DTOA_FIXED, + // Return a fixed number of digits, no matter what the exponent is. + BIGNUM_DTOA_PRECISION + }; + internal class Bignumdtoa + { + static int NormalizedExponent(UInt64 significand, int exponent) + { + unchecked + { + // ASSERT(significand != 0); + while ((significand & Double.kHiddenBit) == 0) + { + significand = significand << 1; + exponent = exponent - 1; + } + return exponent; + } + } + public static void BignumDtoa(double v, BignumDtoaMode mode, int requested_digits, + Vector buffer, ref int length, ref int decimal_point) + { + // ASSERT(v > 0); + // ASSERT(!Double(v).IsSpecial()); + unchecked + { + UInt64 significand; + int exponent; + bool lower_boundary_is_closer; + if (mode == BignumDtoaMode.BIGNUM_DTOA_SHORTEST_SINGLE) + { + float f = (float)v; + // ASSERT(f == v); + significand = new Single(f).Significand(); + exponent = new Single(f).Exponent(); + lower_boundary_is_closer = new Single(f).LowerBoundaryIsCloser(); + } + else + { + significand = new Double(v).Significand(); + exponent = new Double(v).Exponent(); + lower_boundary_is_closer = new Double(v).LowerBoundaryIsCloser(); + } + bool need_boundary_deltas = + (mode == BignumDtoaMode.BIGNUM_DTOA_SHORTEST || mode == BignumDtoaMode.BIGNUM_DTOA_SHORTEST_SINGLE); + + bool is_even = (significand & 1) == 0; + int normalized_exponent = NormalizedExponent(significand, exponent); + // estimated_power might be too low by 1. + int estimated_power = EstimatePower(normalized_exponent); + + // Shortcut for Fixed. + // The requested digits correspond to the digits after the point. If the + // number is much too small, then there is no need in trying to get any + // digits. + if (mode == BignumDtoaMode.BIGNUM_DTOA_FIXED && -estimated_power - 1 > requested_digits) + { + buffer[0] = (byte)'\0'; + length = 0; + // Set decimal-point to -requested_digits. This is what Gay does. + // Note that it should not have any effect anyways since the string is + // empty. + decimal_point = -requested_digits; + return; + } + + Bignum numerator = new Bignum(); + Bignum denominator = new Bignum(); + Bignum delta_minus = new Bignum(); + Bignum delta_plus = new Bignum(); + // Make sure the bignum can grow large enough. The smallest double equals + // 4e-324. In this case the denominator needs fewer than 324*4 binary digits. + // The maximum double is 1.7976931348623157e308 which needs fewer than + // 308*4 binary digits. + // ASSERT(Bignum::kMaxSignificantBits >= 324 * 4); + InitialScaledStartValues(significand, exponent, lower_boundary_is_closer, + estimated_power, need_boundary_deltas, + ref numerator, ref denominator, + ref delta_minus, ref delta_plus); + // We now have v = (numerator / denominator) * 10^estimated_power. + FixupMultiply10(estimated_power, is_even, ref decimal_point, + ref numerator, ref denominator, + ref delta_minus, ref delta_plus); + // We now have v = (numerator / denominator) * 10^(decimal_point-1), and + // 1 <= (numerator + delta_plus) / denominator < 10 + switch (mode) + { + case BignumDtoaMode.BIGNUM_DTOA_SHORTEST: + case BignumDtoaMode.BIGNUM_DTOA_SHORTEST_SINGLE: + GenerateShortestDigits(ref numerator, ref denominator, + ref delta_minus, ref delta_plus, + is_even, buffer, ref length); + break; + case BignumDtoaMode.BIGNUM_DTOA_FIXED: + BignumToFixed(requested_digits, ref decimal_point, + ref numerator, ref denominator, + buffer, ref length); + break; + case BignumDtoaMode.BIGNUM_DTOA_PRECISION: + GenerateCountedDigits(requested_digits, ref decimal_point, + ref numerator, ref denominator, + buffer, ref length); + break; + default: + throw new Exception("Unreachable code"); + //UNREACHABLE(); + } + buffer[length] = (byte)'\0'; + } + } + // The procedure starts generating digits from the left to the right and stops + // when the generated digits yield the shortest decimal representation of v. A + // decimal representation of v is a number lying closer to v than to any other + // double, so it converts to v when read. + // + // This is true if d, the decimal representation, is between m- and m+, the + // upper and lower boundaries. d must be strictly between them if !is_even. + // m- := (numerator - delta_minus) / denominator + // m+ := (numerator + delta_plus) / denominator + // + // Precondition: 0 <= (numerator+delta_plus) / denominator < 10. + // If 1 <= (numerator+delta_plus) / denominator < 10 then no leading 0 digit + // will be produced. This should be the standard precondition. + static void GenerateShortestDigits(ref Bignum numerator, ref Bignum denominator, + ref Bignum delta_minus, ref Bignum delta_plus, + bool is_even, + Vector buffer, ref int length) + { + // Small optimization: if delta_minus and delta_plus are the same just reuse + // one of the two bignums. + if (Bignum.Equal(delta_minus, delta_plus)) + { + delta_plus = delta_minus; + } + length = 0; + for (; ; ) + { + UInt16 digit; + digit = numerator.DivideModuloIntBignum(denominator); + // ASSERT(digit <= 9); // digit is a uint16_t and therefore always positive. + // digit = numerator / denominator (integer division). + // numerator = numerator % denominator. + buffer[length++] = (byte)(digit + '0'); + + // Can we stop already? + // If the remainder of the division is less than the distance to the lower + // boundary we can stop. In this case we simply round down (discarding the + // remainder). + // Similarly we test if we can round up (using the upper boundary). + bool in_delta_room_minus; + bool in_delta_room_plus; + if (is_even) + { + in_delta_room_minus = Bignum.LessEqual(numerator, delta_minus); + } + else + { + in_delta_room_minus = Bignum.Less(numerator, delta_minus); + } + if (is_even) + { + in_delta_room_plus = + Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0; + } + else + { + in_delta_room_plus = + Bignum.PlusCompare(numerator, delta_plus, denominator) > 0; + } + if (!in_delta_room_minus && !in_delta_room_plus) + { + // Prepare for next iteration. + numerator.Times10(); + delta_minus.Times10(); + // We optimized delta_plus to be equal to delta_minus (if they share the + // same value). So don't multiply delta_plus if they point to the same + // object. + if (delta_minus != delta_plus) + { + delta_plus.Times10(); + } + } + else if (in_delta_room_minus && in_delta_room_plus) + { + // Let's see if 2*numerator < denominator. + // If yes, then the next digit would be < 5 and we can round down. + int compare = Bignum.PlusCompare(numerator, numerator, denominator); + if (compare < 0) + { + // Remaining digits are less than .5. . Round down (== do nothing). + } + else if (compare > 0) + { + // Remaining digits are more than .5 of denominator. . Round up. + // Note that the last digit could not be a '9' as otherwise the whole + // loop would have stopped earlier. + // We still have an assert here in case the preconditions were not + // satisfied. + // ASSERT(buffer[(*length) - 1] != '9'); + buffer[(length) - 1]++; + } + else + { + // Halfway case. + // TODO(floitsch): need a way to solve half-way cases. + // For now let's round towards even (since this is what Gay seems to + // do). + + if ((buffer[length - 1] - '0') % 2 == 0) + { + // Round down => Do nothing. + } + else + { + // ASSERT(buffer[(*length) - 1] != '9'); + buffer[length - 1]++; + } + } + return; + } + else if (in_delta_room_minus) + { + // Round down (== do nothing). + return; + } + else + { // in_delta_room_plus + // Round up. + // Note again that the last digit could not be '9' since this would have + // stopped the loop earlier. + // We still have an ASSERT here, in case the preconditions were not + // satisfied. + // ASSERT(buffer[(*length) - 1] != '9'); + buffer[length - 1]++; + return; + } + } + } + // Let v = numerator / denominator < 10. + // Then we generate 'count' digits of d = x.xxxxx... (without the decimal point) + // from left to right. Once 'count' digits have been produced we decide wether + // to round up or down. Remainders of exactly .5 round upwards. Numbers such + // as 9.999999 propagate a carry all the way, and change the + // exponent (decimal_point), when rounding upwards. + static void GenerateCountedDigits(int count, ref int decimal_point, + ref Bignum numerator, ref Bignum denominator, + Vector buffer, ref int length) + { + unchecked + { + // ASSERT(count >= 0); + for (int i = 0; i < count - 1; ++i) + { + UInt16 _digit; + _digit = numerator.DivideModuloIntBignum(denominator); + // ASSERT(_digit <= 9); // digit is a uint16_t and therefore always positive. + // digit = numerator / denominator (integer division). + // numerator = numerator % denominator. + buffer[i] = (byte)(_digit + '0'); + // Prepare for next iteration. + numerator.Times10(); + } + // Generate the last digit. + UInt16 digit; + digit = numerator.DivideModuloIntBignum(denominator); + if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0) + { + digit++; + } + // ASSERT(digit <= 10); + buffer[count - 1] = (byte)(digit + '0'); + // Correct bad digits (in case we had a sequence of '9's). Propagate the + // carry until we hat a non-'9' or til we reach the first digit. + for (int i = count - 1; i > 0; --i) + { + if (buffer[i] != '0' + 10) break; + buffer[i] = (byte)'0'; + buffer[i - 1]++; + } + if (buffer[0] == '0' + 10) + { + // Propagate a carry past the top place. + buffer[0] = (byte)'1'; + decimal_point++; + } + length = count; + } + } + + // Generates 'requested_digits' after the decimal point. It might omit + // trailing '0's. If the input number is too small then no digits at all are + // generated (ex.: 2 fixed digits for 0.00001). + // + // Input verifies: 1 <= (numerator + delta) / denominator < 10. + static void BignumToFixed(int requested_digits, ref int decimal_point, + ref Bignum numerator, ref Bignum denominator, + Vector buffer, ref int length) + { + unchecked + { + // Note that we have to look at more than just the requested_digits, since + // a number could be rounded up. Example: v=0.5 with requested_digits=0. + // Even though the power of v equals 0 we can't just stop here. + if (-(decimal_point) > requested_digits) + { + // The number is definitively too small. + // Ex: 0.001 with requested_digits == 1. + // Set decimal-point to -requested_digits. This is what Gay does. + // Note that it should not have any effect anyways since the string is + // empty. + decimal_point = -requested_digits; + length = 0; + return; + } + else if (-(decimal_point) == requested_digits) + { + // We only need to verify if the number rounds down or up. + // Ex: 0.04 and 0.06 with requested_digits == 1. + // ASSERT(*decimal_point == -requested_digits); + // Initially the fraction lies in range (1, 10]. Multiply the denominator + // by 10 so that we can compare more easily. + denominator.Times10(); + if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0) + { + // If the fraction is >= 0.5 then we have to include the rounded + // digit. + buffer[0] = (byte)'1'; + length = 1; + decimal_point++; + } + else + { + // Note that we caught most of similar cases earlier. + length = 0; + } + return; + } + else + { + // The requested digits correspond to the digits after the point. + // The variable 'needed_digits' includes the digits before the point. + int needed_digits = decimal_point + requested_digits; + GenerateCountedDigits(needed_digits, ref decimal_point, + ref numerator, ref denominator, + buffer, ref length); + } + } + } + + + // Returns an estimation of k such that 10^(k-1) <= v < 10^k where + // v = f * 2^exponent and 2^52 <= f < 2^53. + // v is hence a normalized double with the given exponent. The output is an + // approximation for the exponent of the decimal approimation .digits * 10^k. + // + // The result might undershoot by 1 in which case 10^k <= v < 10^k+1. + // Note: this property holds for v's upper boundary m+ too. + // 10^k <= m+ < 10^k+1. + // (see explanation below). + // + // Examples: + // EstimatePower(0) => 16 + // EstimatePower(-52) => 0 + // + // Note: e >= 0 => EstimatedPower(e) > 0. No similar claim can be made for e<0. + static int EstimatePower(int exponent) + { + unchecked + { + // This function estimates log10 of v where v = f*2^e (with e == exponent). + // Note that 10^floor(log10(v)) <= v, but v <= 10^ceil(log10(v)). + // Note that f is bounded by its container size. Let p = 53 (the double's + // significand size). Then 2^(p-1) <= f < 2^p. + // + // Given that log10(v) == log2(v)/log2(10) and e+(len(f)-1) is quite close + // to log2(v) the function is simplified to (e+(len(f)-1)/log2(10)). + // The computed number undershoots by less than 0.631 (when we compute log3 + // and not log10). + // + // Optimization: since we only need an approximated result this computation + // can be performed on 64 bit integers. On x86/x64 architecture the speedup is + // not really measurable, though. + // + // Since we want to avoid overshooting we decrement by 1e10 so that + // floating-point imprecisions don't affect us. + // + // Explanation for v's boundary m+: the computation takes advantage of + // the fact that 2^(p-1) <= f < 2^p. Boundaries still satisfy this requirement + // (even for denormals where the delta can be much more important). + + const double k1Log10 = 0.30102999566398114; // 1/lg(10) + + // For doubles len(f) == 53 (don't forget the hidden bit). + const int kSignificandSize = Double.kSignificandSize; + double estimate = Math.Ceiling((exponent + kSignificandSize - 1) * k1Log10 - 1e-10); + return (int)estimate; + } + } + // See comments for InitialScaledStartValues. + static void InitialScaledStartValuesPositiveExponent( + UInt64 significand, int exponent, + int estimated_power, bool need_boundary_deltas, + ref Bignum numerator, ref Bignum denominator, + ref Bignum delta_minus, ref Bignum delta_plus) + { + unchecked + { + // A positive exponent implies a positive power. + // ASSERT(estimated_power >= 0); + // Since the estimated_power is positive we simply multiply the denominator + // by 10^estimated_power. + + // numerator = v. + numerator.AssignUInt64(significand); + numerator.ShiftLeft(exponent); + // denominator = 10^estimated_power. + denominator.AssignPowerUInt16(10, estimated_power); + + if (need_boundary_deltas) + { + // Introduce a common denominator so that the deltas to the boundaries are + // integers. + denominator.ShiftLeft(1); + numerator.ShiftLeft(1); + // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common + // denominator (of 2) delta_plus equals 2^e. + delta_plus.AssignUInt16(1); + delta_plus.ShiftLeft(exponent); + // Same for delta_minus. The adjustments if f == 2^p-1 are done later. + delta_minus.AssignUInt16(1); + delta_minus.ShiftLeft(exponent); + } + } + } + + + // See comments for InitialScaledStartValues + static void InitialScaledStartValuesNegativeExponentPositivePower( + UInt64 significand, int exponent, + int estimated_power, bool need_boundary_deltas, + ref Bignum numerator, ref Bignum denominator, + ref Bignum delta_minus, ref Bignum delta_plus) + { + unchecked + { + // v = f * 2^e with e < 0, and with estimated_power >= 0. + // This means that e is close to 0 (have a look at how estimated_power is + // computed). + + // numerator = significand + // since v = significand * 2^exponent this is equivalent to + // numerator = v * / 2^-exponent + numerator.AssignUInt64(significand); + // denominator = 10^estimated_power * 2^-exponent (with exponent < 0) + denominator.AssignPowerUInt16(10, estimated_power); + denominator.ShiftLeft(-exponent); + + if (need_boundary_deltas) + { + // Introduce a common denominator so that the deltas to the boundaries are + // integers. + denominator.ShiftLeft(1); + numerator.ShiftLeft(1); + // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common + // denominator (of 2) delta_plus equals 2^e. + // Given that the denominator already includes v's exponent the distance + // to the boundaries is simply 1. + delta_plus.AssignUInt16(1); + // Same for delta_minus. The adjustments if f == 2^p-1 are done later. + delta_minus.AssignUInt16(1); + } + } + } + + // See comments for InitialScaledStartValues + static void InitialScaledStartValuesNegativeExponentNegativePower( + UInt64 significand, int exponent, + int estimated_power, bool need_boundary_deltas, + ref Bignum numerator, ref Bignum denominator, + ref Bignum delta_minus, ref Bignum delta_plus) + { + // Instead of multiplying the denominator with 10^estimated_power we + // multiply all values (numerator and deltas) by 10^-estimated_power. + + // Use numerator as temporary container for power_ten. + Bignum power_ten = numerator; + power_ten.AssignPowerUInt16(10, -estimated_power); + + if (need_boundary_deltas) + { + // Since power_ten == numerator we must make a copy of 10^estimated_power + // before we complete the computation of the numerator. + // delta_plus = delta_minus = 10^estimated_power + delta_plus.AssignBignum(power_ten); + delta_minus.AssignBignum(power_ten); + } + + // numerator = significand * 2 * 10^-estimated_power + // since v = significand * 2^exponent this is equivalent to + // numerator = v * 10^-estimated_power * 2 * 2^-exponent. + // Remember: numerator has been abused as power_ten. So no need to assign it + // to itself. + // ASSERT(numerator == power_ten); + numerator.MultiplyByUInt64(significand); + + // denominator = 2 * 2^-exponent with exponent < 0. + denominator.AssignUInt16(1); + denominator.ShiftLeft(-exponent); + + if (need_boundary_deltas) + { + // Introduce a common denominator so that the deltas to the boundaries are + // integers. + numerator.ShiftLeft(1); + denominator.ShiftLeft(1); + // With this shift the boundaries have their correct value, since + // delta_plus = 10^-estimated_power, and + // delta_minus = 10^-estimated_power. + // These assignments have been done earlier. + // The adjustments if f == 2^p-1 (lower boundary is closer) are done later. + } + } + // Let v = significand * 2^exponent. + // Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator + // and denominator. The functions GenerateShortestDigits and + // GenerateCountedDigits will then convert this ratio to its decimal + // representation d, with the required accuracy. + // Then d * 10^estimated_power is the representation of v. + // (Note: the fraction and the estimated_power might get adjusted before + // generating the decimal representation.) + // + // The initial start values consist of: + // - a scaled numerator: s.t. numerator/denominator == v / 10^estimated_power. + // - a scaled (common) denominator. + // optionally (used by GenerateShortestDigits to decide if it has the shortest + // decimal converting back to v): + // - v - m-: the distance to the lower boundary. + // - m+ - v: the distance to the upper boundary. + // + // v, m+, m-, and therefore v - m- and m+ - v all share the same denominator. + // + // Let ep == estimated_power, then the returned values will satisfy: + // v / 10^ep = numerator / denominator. + // v's boundarys m- and m+: + // m- / 10^ep == v / 10^ep - delta_minus / denominator + // m+ / 10^ep == v / 10^ep + delta_plus / denominator + // Or in other words: + // m- == v - delta_minus * 10^ep / denominator; + // m+ == v + delta_plus * 10^ep / denominator; + // + // Since 10^(k-1) <= v < 10^k (with k == estimated_power) + // or 10^k <= v < 10^(k+1) + // we then have 0.1 <= numerator/denominator < 1 + // or 1 <= numerator/denominator < 10 + // + // It is then easy to kickstart the digit-generation routine. + // + // The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST + // or BIGNUM_DTOA_SHORTEST_SINGLE. + + static void InitialScaledStartValues(UInt64 significand, + int exponent, + bool lower_boundary_is_closer, + int estimated_power, + bool need_boundary_deltas, + ref Bignum numerator, + ref Bignum denominator, + ref Bignum delta_minus, + ref Bignum delta_plus) + { + if (exponent >= 0) + { + InitialScaledStartValuesPositiveExponent( + significand, exponent, estimated_power, need_boundary_deltas, + ref numerator, ref denominator, ref delta_minus, ref delta_plus); + } + else if (estimated_power >= 0) + { + InitialScaledStartValuesNegativeExponentPositivePower( + significand, exponent, estimated_power, need_boundary_deltas, + ref numerator, ref denominator, ref delta_minus, ref delta_plus); + } + else + { + InitialScaledStartValuesNegativeExponentNegativePower( + significand, exponent, estimated_power, need_boundary_deltas, + ref numerator, ref denominator, ref delta_minus, ref delta_plus); + } + + if (need_boundary_deltas && lower_boundary_is_closer) + { + // The lower boundary is closer at half the distance of "normal" numbers. + // Increase the common denominator and adapt all but the delta_minus. + denominator.ShiftLeft(1); // *2 + numerator.ShiftLeft(1); // *2 + delta_plus.ShiftLeft(1); // *2 + } + } + // This routine multiplies numerator/denominator so that its values lies in the + // range 1-10. That is after a call to this function we have: + // 1 <= (numerator + delta_plus) /denominator < 10. + // Let numerator the input before modification and numerator' the argument + // after modification, then the output-parameter decimal_point is such that + // numerator / denominator * 10^estimated_power == + // numerator' / denominator' * 10^(decimal_point - 1) + // In some cases estimated_power was too low, and this is already the case. We + // then simply adjust the power so that 10^(k-1) <= v < 10^k (with k == + // estimated_power) but do not touch the numerator or denominator. + // Otherwise the routine multiplies the numerator and the deltas by 10. + static void FixupMultiply10(int estimated_power, bool is_even, + ref int decimal_point, + ref Bignum numerator, ref Bignum denominator, + ref Bignum delta_minus, ref Bignum delta_plus) + { + bool in_range; + if (is_even) + { + // For IEEE doubles half-way cases (in decimal system numbers ending with 5) + // are rounded to the closest floating-point number with even significand. + in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0; + } + else + { + in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0; + } + if (in_range) + { + // Since numerator + delta_plus >= denominator we already have + // 1 <= numerator/denominator < 10. Simply update the estimated_power. + decimal_point = estimated_power + 1; + } + else + { + decimal_point = estimated_power; + numerator.Times10(); + if (Bignum.Equal(delta_minus, delta_plus)) + { + delta_minus.Times10(); + delta_plus.AssignBignum(delta_minus); + } + else + { + delta_minus.Times10(); + delta_plus.Times10(); + } + } + } + } +} \ No newline at end of file diff --git a/src/Utf8Json/Internal/DoubleConversion/DoubleToStringConverter.cs b/src/Utf8Json/Internal/DoubleConversion/DoubleToStringConverter.cs old mode 100644 new mode 100755 index b371a6a5..58589a72 --- a/src/Utf8Json/Internal/DoubleConversion/DoubleToStringConverter.cs +++ b/src/Utf8Json/Internal/DoubleConversion/DoubleToStringConverter.cs @@ -585,8 +585,8 @@ static bool Grisu3(double v, } static bool FastDtoa(double v, - FastDtoaMode mode, - // int requested_digits, + FastDtoaMode mode, + // int requested_digits, byte[] buffer, out int length, out int decimal_point) @@ -657,17 +657,9 @@ static bool ToShortestIeeeNumber( var decimal_rep = GetDecimalRepBuffer(kDecimalRepCapacity); // byte[] decimal_rep = new byte[kDecimalRepCapacity]; int decimal_rep_length; - var fastworked = DoubleToAscii(value, mode, 0, decimal_rep, + DoubleToAscii(value, mode, 0, decimal_rep, out sign, out decimal_rep_length, out decimal_point); - if (!fastworked) - { - // C# custom, slow path - var str = value.ToString("G17", CultureInfo.InvariantCulture); - result_builder.AddStringSlow(str); - return true; - } - bool unique_zero = (flags_ & Flags.UNIQUE_ZERO) != 0; if (sign && (value != 0.0 || !unique_zero)) { @@ -787,14 +779,29 @@ static void CreateExponentialRepresentation( } result_builder.AddSubstring(buffer, first_char_pos, kMaxExponentLength - first_char_pos); } - - // modified, return fast_worked. - static bool DoubleToAscii(double v, + static BignumDtoaMode DtoaToBignumDtoaMode( + DtoaMode dtoa_mode) + { + switch (dtoa_mode) + { + case DtoaMode.SHORTEST: + return BignumDtoaMode.BIGNUM_DTOA_SHORTEST; + case DtoaMode.SHORTEST_SINGLE: + return BignumDtoaMode.BIGNUM_DTOA_SHORTEST_SINGLE; + // case DtoaMode.FIXED: return BignumDtoaMode.BIGNUM_DTOA_FIXED; + // case DtoaMode.PRECISION: return BignumDtoaMode.BIGNUM_DTOA_PRECISION; + default: + throw new Exception("Unreachable code"); + // UNREACHABLE(); + } + } + + static void DoubleToAscii(double v, DtoaMode mode, - int requested_digits, - //byte[] buffer, - //int buffer_length, - byte[] vector, // already allocate + int requested_digits, + //byte[] buffer, + //int buffer_length, + byte[] buffer, // already allocate out bool sign, out int length, out int point) @@ -818,21 +825,21 @@ static bool DoubleToAscii(double v, if (v == 0) { - vector[0] = (byte)'0'; + buffer[0] = (byte)'0'; // vector[1] = '\0'; length = 1; point = 1; - return true; + return; } bool fast_worked; switch (mode) { case DtoaMode.SHORTEST: - fast_worked = FastDtoa(v, FastDtoaMode.FAST_DTOA_SHORTEST, vector, out length, out point); + fast_worked = FastDtoa(v, FastDtoaMode.FAST_DTOA_SHORTEST, buffer, out length, out point); break; case DtoaMode.SHORTEST_SINGLE: - fast_worked = FastDtoa(v, FastDtoaMode.FAST_DTOA_SHORTEST_SINGLE, vector, out length, out point); + fast_worked = FastDtoa(v, FastDtoaMode.FAST_DTOA_SHORTEST_SINGLE, buffer, out length, out point); break; //case FIXED: // fast_worked = FastFixedDtoa(v, requested_digits, vector, length, point); @@ -845,14 +852,13 @@ static bool DoubleToAscii(double v, fast_worked = false; throw new Exception("Unreachable code."); } - // if (fast_worked) return; + if (fast_worked) return; // If the fast dtoa didn't succeed use the slower bignum version. - // BignumDtoaMode bignum_mode = DtoaToBignumDtoaMode(mode); - // BignumDtoa(v, bignum_mode, requested_digits, vector, length, point); - // vector[*length] = '\0'; - - return fast_worked; + BignumDtoaMode bignum_mode = DtoaToBignumDtoaMode(mode); + Vector vector = new Vector(buffer, 0, buffer.Length); + Bignumdtoa.BignumDtoa(v, bignum_mode, requested_digits, vector, ref length, ref point); + vector[length] = (byte)'\0'; } } } \ No newline at end of file diff --git a/src/Utf8Json/Internal/DoubleConversion/IEEE.cs b/src/Utf8Json/Internal/DoubleConversion/IEEE.cs old mode 100644 new mode 100755 index 8d7d470c..2777cb40 --- a/src/Utf8Json/Internal/DoubleConversion/IEEE.cs +++ b/src/Utf8Json/Internal/DoubleConversion/IEEE.cs @@ -47,6 +47,11 @@ public Double(double d) d64_ = new UnionDoubleULong { d = d }.u64; } + public Double(ulong d64) + { + d64_ = d64; + } + public Double(DiyFp d) { d64_ = DiyFpToUint64(d); diff --git a/src/Utf8Json/Internal/DoubleConversion/StringToDouble.cs b/src/Utf8Json/Internal/DoubleConversion/StringToDouble.cs old mode 100644 new mode 100755 index 0dc22759..8145382f --- a/src/Utf8Json/Internal/DoubleConversion/StringToDouble.cs +++ b/src/Utf8Json/Internal/DoubleConversion/StringToDouble.cs @@ -6,20 +6,20 @@ namespace Utf8Json.Internal.DoubleConversion { using uint64_t = UInt64; - internal struct Vector + internal struct Vector { - public readonly byte[] bytes; + public readonly T[] bytes; public readonly int start; public readonly int _length; - public Vector(byte[] bytes, int start, int length) + public Vector(T[] bytes, int start, int length) { this.bytes = bytes; this.start = start; this._length = length; } - public byte this[int i] + public T this[int i] { get { @@ -36,12 +36,12 @@ public int length() return _length; } - public byte first() + public T first() { return bytes[start]; } - public byte last() + public T last() { return bytes[_length - 1]; } @@ -51,9 +51,9 @@ public bool is_empty() return _length == 0; } - public Vector SubVector(int from, int to) + public Vector SubVector(int from, int to) { - return new Vector(this.bytes, start + from, to - from); + return new Vector(this.bytes, start + from, to - from); } } @@ -123,7 +123,7 @@ static byte[] GetCopyBuffer() // we round up to 780. const int kMaxSignificantDecimalDigits = 780; - static Vector TrimLeadingZeros(Vector buffer) + static Vector TrimLeadingZeros(Vector buffer) { for (int i = 0; i < buffer.length(); i++) { @@ -132,10 +132,10 @@ static Vector TrimLeadingZeros(Vector buffer) return buffer.SubVector(i, buffer.length()); } } - return new Vector(buffer.bytes, buffer.start, 0); + return new Vector(buffer.bytes, buffer.start, 0); } - static Vector TrimTrailingZeros(Vector buffer) + static Vector TrimTrailingZeros(Vector buffer) { for (int i = buffer.length() - 1; i >= 0; --i) { @@ -144,11 +144,11 @@ static Vector TrimTrailingZeros(Vector buffer) return buffer.SubVector(0, i + 1); } } - return new Vector(buffer.bytes, buffer.start, 0); + return new Vector(buffer.bytes, buffer.start, 0); } - static void CutToMaxSignificantDigits(Vector buffer, + static void CutToMaxSignificantDigits(Vector buffer, int exponent, byte[] significant_buffer, out int significant_exponent) @@ -170,19 +170,19 @@ static void CutToMaxSignificantDigits(Vector buffer, // If possible the input-buffer is reused, but if the buffer needs to be // modified (due to cutting), then the input needs to be copied into the // buffer_copy_space. - static void TrimAndCut(Vector buffer, int exponent, + static void TrimAndCut(Vector buffer, int exponent, byte[] buffer_copy_space, int space_size, - out Vector trimmed, out int updated_exponent) + out Vector trimmed, out int updated_exponent) { - Vector left_trimmed = TrimLeadingZeros(buffer); - Vector right_trimmed = TrimTrailingZeros(left_trimmed); + Vector left_trimmed = TrimLeadingZeros(buffer); + Vector right_trimmed = TrimTrailingZeros(left_trimmed); exponent += left_trimmed.length() - right_trimmed.length(); if (right_trimmed.length() > kMaxSignificantDecimalDigits) { // (void)space_size; // Mark variable as used. CutToMaxSignificantDigits(right_trimmed, exponent, buffer_copy_space, out updated_exponent); - trimmed = new Vector(buffer_copy_space, 0, kMaxSignificantDecimalDigits); + trimmed = new Vector(buffer_copy_space, 0, kMaxSignificantDecimalDigits); } else { @@ -197,7 +197,7 @@ static void TrimAndCut(Vector buffer, int exponent, // When the string starts with "1844674407370955161" no further digit is read. // Since 2^64 = 18446744073709551616 it would still be possible read another // digit if it was less or equal than 6, but this would complicate the code. - static uint64_t ReadUint64(Vector buffer, + static uint64_t ReadUint64(Vector buffer, out int number_of_read_digits) { uint64_t result = 0; @@ -215,7 +215,7 @@ static uint64_t ReadUint64(Vector buffer, // The returned DiyFp is not necessarily normalized. // If remaining_decimals is zero then the returned DiyFp is accurate. // Otherwise it has been rounded and has error of at most 1/2 ulp. - static void ReadDiyFp(Vector buffer, + static void ReadDiyFp(Vector buffer, out DiyFp result, out int remaining_decimals) { @@ -241,7 +241,7 @@ static void ReadDiyFp(Vector buffer, } - static bool DoubleStrtod(Vector trimmed, + static bool DoubleStrtod(Vector trimmed, int exponent, out double result) { @@ -310,7 +310,7 @@ static DiyFp AdjustmentPowerOfTen(int exponent) // If the function returns true then the result is the correct double. // Otherwise it is either the correct double or the double that is just below // the correct double. - static bool DiyFpStrtod(Vector buffer, + static bool DiyFpStrtod(Vector buffer, int exponent, out double result) { @@ -422,10 +422,47 @@ static bool DiyFpStrtod(Vector buffer, return true; } } - + // Returns + // - -1 if buffer*10^exponent < diy_fp. + // - 0 if buffer*10^exponent == diy_fp. + // - +1 if buffer*10^exponent > diy_fp. + // Preconditions: + // buffer.length() + exponent <= kMaxDecimalPower + 1 + // buffer.length() + exponent > kMinDecimalPower + // buffer.length() <= kMaxDecimalSignificantDigits + static int CompareBufferWithDiyFp(Vector buffer, + int exponent, + DiyFp diy_fp) + { + // Make sure that the Bignum will be able to hold all our numbers. + // Our Bignum implementation has a separate field for exponents. Shifts will + // consume at most one bigit (< 64 bits). + // ln(10) == 3.3219...\ + Bignum buffer_bignum = new Bignum(); + Bignum diy_fp_bignum = new Bignum(); + buffer_bignum.AssignDecimalString(buffer); + diy_fp_bignum.AssignUInt64(diy_fp.f); + if (exponent >= 0) + { + buffer_bignum.MultiplyByPowerOfTen(exponent); + } + else + { + diy_fp_bignum.MultiplyByPowerOfTen(-exponent); + } + if (diy_fp.e > 0) + { + diy_fp_bignum.ShiftLeft(diy_fp.e); + } + else + { + buffer_bignum.ShiftLeft(-diy_fp.e); + } + return Bignum.Compare(buffer_bignum, diy_fp_bignum); + } // Returns true if the guess is the correct double. // Returns false, when guess is either correct or the next-lower double. - static bool ComputeGuess(Vector trimmed, int exponent, + static bool ComputeGuess(Vector trimmed, int exponent, out double guess) { if (trimmed.length() == 0) @@ -456,10 +493,10 @@ static bool ComputeGuess(Vector trimmed, int exponent, return false; } - public static double? Strtod(Vector buffer, int exponent) + public static double Strtod(Vector buffer, int exponent) { byte[] copy_buffer = GetCopyBuffer(); - Vector trimmed; + Vector trimmed; int updated_exponent; TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits, out trimmed, out updated_exponent); @@ -468,13 +505,60 @@ static bool ComputeGuess(Vector trimmed, int exponent, double guess; var is_correct = ComputeGuess(trimmed, exponent, out guess); if (is_correct) return guess; - return null; + DiyFp upper_boundary = new Double(guess).UpperBoundary(); + int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary); + if (comparison < 0) + { + return guess; + } + else if (comparison > 0) + { + return new Double(guess).NextDouble(); + } + else if ((new Double(guess).Significand() & 1) == 0) + { + // Round towards even. + return guess; + } + else + { + return new Double(guess).NextDouble(); + } } - - public static float? Strtof(Vector buffer, int exponent) + static float SanitizedDoubletof(double d) + { + //ASSERT(d >= 0.0); + // ASAN has a sanitize check that disallows casting doubles to floats if + // they are too big. + // https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#available-checks + // The behavior should be covered by IEEE 754, but some projects use this + // flag, so work around it. + float max_finite = 3.4028234663852885981170418348451692544e+38f; + // The half-way point between the max-finite and infinity value. + // Since infinity has an even significand everything equal or greater than + // this value should become infinity. + double half_max_finite_infinity = + 3.40282356779733661637539395458142568448e+38; + if (d >= max_finite) + { + if (d >= half_max_finite_infinity) + { + return Single.Infinity(); + } + else + { + return max_finite; + } + } + else + { + return (float)d; + } + } + public static float Strtof(Vector buffer, int exponent) { byte[] copy_buffer = GetCopyBuffer(); - Vector trimmed; + Vector trimmed; int updated_exponent; TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits, out trimmed, out updated_exponent); @@ -518,18 +602,53 @@ static bool ComputeGuess(Vector trimmed, int exponent, else { double double_next2 = new Double(double_next).NextDouble(); - f4 = (float)(double_next2); - } - // (void)f2; // Mark variable as used. + f4 = SanitizedDoubletof(double_next2); + } + // (void)f2; // Mark variable as used. + // ASSERT(f1 <= f2 && f2 <= f3 && f3 <= f4); // If the guess doesn't lie near a single-precision boundary we can simply // return its float-value. if (f1 == f4) { return float_guess; + } + // ASSERT((f1 != f2 && f2 == f3 && f3 == f4) || + // (f1 == f2 && f2 != f3 && f3 == f4) || + // (f1 == f2 && f2 == f3 && f3 != f4)); + + // guess and next are the two possible candidates (in the same way that + // double_guess was the lower candidate for a double-precision guess). + float guess = f1; + float next = f4; + DiyFp upper_boundary; + if (guess == 0.0f) + { + float min_float = 1e-45f; + upper_boundary = new Double(((double)min_float) / 2).AsDiyFp(); + } + else + { + upper_boundary = new Single(guess).UpperBoundary(); + } + int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary); + if (comparison < 0) + { + return guess; + } + else if (comparison > 0) + { + return next; + } + else if ((new Single(guess).Significand() & 1) == 0) + { + // Round towards even. + return guess; + } + else + { + return next; } - - return null; } } } \ No newline at end of file diff --git a/src/Utf8Json/Internal/DoubleConversion/StringToDoubleConverter.cs b/src/Utf8Json/Internal/DoubleConversion/StringToDoubleConverter.cs old mode 100644 new mode 100755 index 8227393a..597ed2a4 --- a/src/Utf8Json/Internal/DoubleConversion/StringToDoubleConverter.cs +++ b/src/Utf8Json/Internal/DoubleConversion/StringToDoubleConverter.cs @@ -588,35 +588,18 @@ static double StringToIeee( buffer[buffer_pos] = (byte)'\0'; - double? converted; + double converted; if (read_as_double) { - converted = StringToDouble.Strtod(new Vector(buffer, 0, buffer_pos), exponent); + converted = StringToDouble.Strtod(new Vector(buffer, 0, buffer_pos), exponent); } else { - converted = StringToDouble.Strtof(new Vector(buffer, 0, buffer_pos), exponent); - } - - if (converted == null) - { - // read-again - processed_characters_count = (current - input); - - var fallbackbuffer = GetFallbackBuffer(); - BinaryUtil.EnsureCapacity(ref fallbackBuffer, 0, processed_characters_count); - var fallbackI = 0; - while (input != current) - { - fallbackbuffer[fallbackI++] = input.Value; - input++; - } - var laststr = Encoding.UTF8.GetString(fallbackbuffer, 0, fallbackI); - return double.Parse(laststr); + converted = StringToDouble.Strtof(new Vector(buffer, 0, buffer_pos), exponent); } processed_characters_count = (current - input); - return sign ? -converted.Value : converted.Value; + return sign ? -converted : converted; } } }