Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(485)

Unified Diff: runtime/vm/bigint_operations.cc

Issue 9540015: Implement correct bigint to double conversion. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments. Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/vm/bigint_operations.h ('k') | runtime/vm/bigint_operations_test.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/vm/bigint_operations.cc
diff --git a/runtime/vm/bigint_operations.cc b/runtime/vm/bigint_operations.cc
index b62842b1993dfa5036ad8900201c7c78aaee7677..cbc88a9a19a8c6dce4ca5e1d5048e85163953a9e 100644
--- a/runtime/vm/bigint_operations.cc
+++ b/runtime/vm/bigint_operations.cc
@@ -465,14 +465,136 @@ RawSmi* BigintOperations::ToSmi(const Bigint& bigint) {
RawDouble* BigintOperations::ToDouble(const Bigint& bigint) {
- // TODO(floitsch/benl): This is a quick and dirty implementation to unblock
- // other areas of the code. It does not handle all bit-twiddling correctly.
- const double shift_value = (1 << kDigitBitSize);
- double value = 0.0;
- for (int i = bigint.Length() - 1; i >= 0; i--) {
- value *= shift_value;
- value += static_cast<double>(bigint.GetChunkAt(i));
+ ASSERT(IsClamped(bigint));
+ if (bigint.IsZero()) {
+ return Double::New(0.0);
+ }
+ if (AbsFitsIntoUint64(bigint)) {
+ double absolute_value = static_cast<double>(AbsToUint64(bigint));
+ double result = bigint.IsNegative() ? -absolute_value : absolute_value;
+ return Double::New(result);
+ }
+
+ static const int kPhysicalSignificandSize = 52;
+ // The significand size has an additional hidden bit.
+ static const int kSignificandSize = kPhysicalSignificandSize + 1;
+ static const int kExponentBias = 0x3FF + kPhysicalSignificandSize;
+ static const int kMaxExponent = 0x7FF - kExponentBias;
+ static const uint64_t kOne64 = 1;
+ static const uint64_t kInfinityBits =
+ DART_2PART_UINT64_C(0x7FF00000, 00000000);
+
+ // A double is composed of an exponent e and a significand s. Its value equals
+ // s * 2^e. The significand has 53 bits of which the first one must always be
+ // 1 (at least for then numbers we are working with here) and is therefore
+ // omitted. The physical size of the significand is thus 52 bits.
+ // The exponent has 11 bits and is biased by 0x3FF + 52. For example an
+ // exponent e = 10 is written as 0x3FF + 52 + 10 (in the 11 bits that are
+ // reserved for the exponent).
+ // When converting the given bignum to a double we have to pay attention to
+ // the rounding. In particular we have to decide which double to pick if an
+ // input lies exactly between two doubles. As usual with double operations
+ // we pick the double with an even significand in such cases.
+ //
+ // General approach of this algorithm: Get 54 bits (one more than the
+ // significand size) of the bigint. If the last bit is then 1, then (without
+ // knowledge of the remaining bits) we could have a half-way number.
+ // If the second-to-last bit is odd then we know that we have to round up:
+ // if the remaining bits are not zero then the input lies closer to the higher
+ // double. If the remaining bits are zero then we have a half-way case and
+ // we need to round up too (rounding to the even double).
+ // If the second-to-last bit is even then we need to look at the remaining
+ // bits to determine if any of them is not zero. If that's the case then the
+ // number lies closer to the next-higher double. Otherwise we round the
+ // half-way case down to even.
+
+ intptr_t length = bigint.Length();
+ if (((length - 1) * kDigitBitSize) > (kMaxExponent + kSignificandSize)) {
+ // Does not fit into a double.
+ double infinity = bit_cast<double>(kInfinityBits);
+ return Double::New(bigint.IsNegative() ? -infinity : infinity);
+ }
+
+
+ intptr_t digit_index = length - 1;
+ // In order to round correctly we need to look at half-way cases. Therefore we
+ // get kSignificandSize + 1 bits. If the last bit is 1 then we have to look
+ // at the remaining bits to know if we have to round up.
+ int needed_bits = kSignificandSize + 1;
+ ASSERT((kDigitBitSize < needed_bits) && (2 * kDigitBitSize >= needed_bits));
+ bool discarded_bits_were_zero = true;
+
+ Chunk firstDigit = bigint.GetChunkAt(digit_index--);
+ uint64_t twice_significand_floor = firstDigit;
+ intptr_t twice_significant_exponent = (digit_index + 1) * kDigitBitSize;
+ needed_bits -= CountBits(firstDigit);
+
+ if (needed_bits >= kDigitBitSize) {
+ twice_significand_floor <<= kDigitBitSize;
+ twice_significand_floor |= bigint.GetChunkAt(digit_index--);
+ twice_significant_exponent -= kDigitBitSize;
+ needed_bits -= kDigitBitSize;
+ }
+ if (needed_bits > 0) {
+ ASSERT(needed_bits <= kDigitBitSize);
+ Chunk digit = bigint.GetChunkAt(digit_index--);
+ int discarded_bits_count = kDigitBitSize - needed_bits;
+ twice_significand_floor <<= needed_bits;
+ twice_significand_floor |= digit >> discarded_bits_count;
+ twice_significant_exponent -= needed_bits;
+ uint64_t discarded_bits_mask = (kOne64 << discarded_bits_count) - 1;
+ discarded_bits_were_zero = ((digit & discarded_bits_mask) == 0);
+ }
+ ASSERT((twice_significand_floor >> kSignificandSize) == 1);
+
+ // We might need to round up the significand later.
+ uint64_t significand = twice_significand_floor >> 1;
+ intptr_t exponent = twice_significant_exponent + 1;
+
+ if (exponent >= kMaxExponent) {
+ // Infinity.
+ // Does not fit into a double.
+ double infinity = bit_cast<double>(kInfinityBits);
+ return Double::New(bigint.IsNegative() ? -infinity : infinity);
+ }
+
+ if ((twice_significand_floor & 1) == 1) {
+ bool round_up = false;
+
+ if ((significand & 1) != 0 || !discarded_bits_were_zero) {
+ // Even if the remaining bits are zero we still need to round up since we
+ // want to round to even for half-way cases.
+ round_up = true;
+ } else {
+ // Could be a half-way case. See if the remaining bits are non-zero.
+ for (intptr_t i = 0; i <= digit_index; i++) {
+ if (bigint.GetChunkAt(i) != 0) {
+ round_up = true;
+ break;
+ }
+ }
+ }
+
+ if (round_up) {
+ significand++;
+ // It might be that we just went from 53 bits to 54 bits.
+ // Example: After adding 1 to 1FFF..FF (with 53 bits set to 1) we have
+ // 2000..00 (= 2 ^ 54). When adding the exponent and significand together
+ // this will increase the exponent by 1 which is exactly what we want.
+ }
}
+
+ ASSERT((significand >> (kSignificandSize - 1)) == 1
+ || significand == kOne64 << kSignificandSize);
+ uint64_t biased_exponent = exponent + kExponentBias;
+ // The significand still has the hidden bit. We simply decrement the biased
+ // exponent by one instead of playing around with the significand.
+ biased_exponent--;
+ // Note that we must use the plus operator instead of bit-or.
+ uint64_t double_bits =
+ (biased_exponent << kPhysicalSignificandSize) + significand;
+
+ double value = bit_cast<double>(double_bits);
if (bigint.IsNegative()) {
value = -value;
}
@@ -528,6 +650,7 @@ bool BigintOperations::FitsIntoMint(const Bigint& bigint) {
uint64_t BigintOperations::AbsToUint64(const Bigint& bigint) {
+ ASSERT(AbsFitsIntoUint64(bigint));
uint64_t value = 0;
for (int i = bigint.Length() - 1; i >= 0; i--) {
value <<= kDigitBitSize;
@@ -547,8 +670,7 @@ int64_t BigintOperations::ToMint(const Bigint& bigint) {
}
-bool BigintOperations::FitsIntoUint64(const Bigint& bigint) {
- if (bigint.IsNegative()) return false;
+bool BigintOperations::AbsFitsIntoUint64(const Bigint& bigint) {
intptr_t b_length = bigint.Length();
int num_bits = CountBits(bigint.GetChunkAt(b_length - 1));
num_bits += (kDigitBitSize * (b_length - 1));
@@ -557,6 +679,12 @@ bool BigintOperations::FitsIntoUint64(const Bigint& bigint) {
}
+bool BigintOperations::FitsIntoUint64(const Bigint& bigint) {
+ if (bigint.IsNegative()) return false;
+ return AbsFitsIntoUint64(bigint);
+}
+
+
uint64_t BigintOperations::ToUint64(const Bigint& bigint) {
ASSERT(FitsIntoUint64(bigint));
return AbsToUint64(bigint);
« no previous file with comments | « runtime/vm/bigint_operations.h ('k') | runtime/vm/bigint_operations_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698