Bug 1558538 - BigInt-to-Number conversion is rather borken. r=wingo
☠☠ backed out by 265a8451194c ☠ ☠
authorJeff Walden <jwalden@mit.edu>
Wed, 19 Jun 2019 15:24:17 +0000
changeset 479222 45210f041ea71be2f52d7e7c66cf9cb1fb8659c7
parent 479221 78293863e0e9bf42bf57fa3b82390da304d162df
child 479223 265a8451194c0a558fde24db7bb44982ad4a8c8e
push id88155
push userjwalden@mit.edu
push dateWed, 19 Jun 2019 15:25:02 +0000
treeherderautoland@45210f041ea7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswingo
bugs1558538
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1558538 - BigInt-to-Number conversion is rather borken. r=wingo Differential Revision: https://phabricator.services.mozilla.com/D34678
js/src/tests/non262/BigInt/Number-conversion-rounding.js
js/src/vm/BigIntType.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/BigInt/Number-conversion-rounding.js
@@ -0,0 +1,187 @@
+// |reftest| skip-if(!this.hasOwnProperty("BigInt"))
+// Any copyright is dedicated to the Public Domain.
+// https://creativecommons.org/licenses/publicdomain/
+
+/**
+ * Simple single-Digit on x64, double-Digit on x86 tests.
+ */
+
+assertEq(BigInt(Number(2n**53n - 2n)), 2n**53n - 2n);
+assertEq(BigInt(Number(2n**53n - 1n)), 2n**53n - 1n);
+assertEq(BigInt(Number(2n**53n)), 2n**53n);
+assertEq(BigInt(Number(2n**53n + 1n)), 2n**53n);
+assertEq(BigInt(Number(2n**53n + 2n)), 2n**53n + 2n);
+assertEq(BigInt(Number(2n**53n + 3n)), 2n**53n + 4n);
+assertEq(BigInt(Number(2n**53n + 4n)), 2n**53n + 4n);
+assertEq(BigInt(Number(2n**53n + 5n)), 2n**53n + 4n);
+assertEq(BigInt(Number(2n**53n + 6n)), 2n**53n + 6n);
+assertEq(BigInt(Number(2n**53n + 7n)), 2n**53n + 8n);
+assertEq(BigInt(Number(2n**53n + 8n)), 2n**53n + 8n);
+
+assertEq(BigInt(Number(2n**54n - 4n)), 2n**54n - 4n);
+assertEq(BigInt(Number(2n**54n - 3n)), 2n**54n - 4n);
+assertEq(BigInt(Number(2n**54n - 2n)), 2n**54n - 2n);
+assertEq(BigInt(Number(2n**54n - 1n)), 2n**54n);
+assertEq(BigInt(Number(2n**54n)), 2n**54n);
+assertEq(BigInt(Number(2n**54n + 1n)), 2n**54n);
+assertEq(BigInt(Number(2n**54n + 2n)), 2n**54n);
+assertEq(BigInt(Number(2n**54n + 3n)), 2n**54n + 4n);
+assertEq(BigInt(Number(2n**54n + 4n)), 2n**54n + 4n);
+assertEq(BigInt(Number(2n**54n + 5n)), 2n**54n + 4n);
+assertEq(BigInt(Number(2n**54n + 6n)), 2n**54n + 8n);
+assertEq(BigInt(Number(2n**54n + 7n)), 2n**54n + 8n);
+assertEq(BigInt(Number(2n**54n + 8n)), 2n**54n + 8n);
+
+assertEq(BigInt(Number(2n**55n - 8n)), 2n**55n - 8n);
+assertEq(BigInt(Number(2n**55n - 7n)), 2n**55n - 8n);
+assertEq(BigInt(Number(2n**55n - 6n)), 2n**55n - 8n);
+assertEq(BigInt(Number(2n**55n - 5n)), 2n**55n - 4n);
+assertEq(BigInt(Number(2n**55n - 4n)), 2n**55n - 4n);
+assertEq(BigInt(Number(2n**55n - 3n)), 2n**55n - 4n);
+assertEq(BigInt(Number(2n**55n - 2n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n - 1n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n + 1n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n + 2n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n + 3n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n + 4n)), 2n**55n);
+assertEq(BigInt(Number(2n**55n + 5n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 6n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 7n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 8n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 9n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 10n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 11n)), 2n**55n + 8n);
+assertEq(BigInt(Number(2n**55n + 12n)), 2n**55n + 16n);
+assertEq(BigInt(Number(2n**55n + 13n)), 2n**55n + 16n);
+assertEq(BigInt(Number(2n**55n + 14n)), 2n**55n + 16n);
+assertEq(BigInt(Number(2n**55n + 15n)), 2n**55n + 16n);
+assertEq(BigInt(Number(2n**55n + 16n)), 2n**55n + 16n);
+
+
+/**
+ * Simple double-Digit on x64, triple-Digit on x86 tests.
+ */
+
+// The tests below that aren't subtracting bits will have no bits in the
+// ultimate significand from the most-significant digit (because of the implicit
+// one being excluded).
+assertEq(BigInt(Number(2n**64n - 2n**11n)), 2n**64n - 2n**11n);
+assertEq(BigInt(Number(2n**64n - 2n**11n + 2n**10n - 1n)), 2n**64n - 2n**11n);
+assertEq(BigInt(Number(2n**64n - 2n**11n + 2n**10n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n - 2n**10n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n + 1n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n + 2n**5n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n + 2n**10n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n + 2n**11n)), 2n**64n);
+assertEq(BigInt(Number(2n**64n + 2n**11n + 1n)), 2n**64n + 2n**12n);
+assertEq(BigInt(Number(2n**64n + 2n**12n)), 2n**64n + 2n**12n);
+assertEq(BigInt(Number(2n**64n + 2n**12n + 1n)), 2n**64n + 2n**12n);
+assertEq(BigInt(Number(2n**64n + 2n**12n + 2n**5n)), 2n**64n + 2n**12n);
+assertEq(BigInt(Number(2n**64n + 2n**12n + 2n**10n)), 2n**64n + 2n**12n);
+assertEq(BigInt(Number(2n**64n + 2n**12n + 2n**11n - 1n)), 2n**64n + 2n**12n);
+assertEq(BigInt(Number(2n**64n + 2n**12n + 2n**11n)), 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**64n + 2n**12n + 2n**11n + 1n)), 2n**64n + 2n**13n);
+
+// These tests *will* have a bit from the most-significant digit in the ultimate
+// significand.
+assertEq(BigInt(Number(2n**65n - 2n**12n)), 2n**65n - 2n**12n);
+assertEq(BigInt(Number(2n**65n - 2n**12n + 2n**11n - 1n)), 2n**65n - 2n**12n);
+assertEq(BigInt(Number(2n**65n - 2n**12n + 2n**11n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n - 2n**11n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n + 1n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n + 2n**5n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n + 2n**11n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n + 2n**12n)), 2n**65n);
+assertEq(BigInt(Number(2n**65n + 2n**12n + 1n)), 2n**65n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**13n)), 2n**65n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**13n + 1n)), 2n**65n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**13n + 2n**5n)), 2n**65n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**13n + 2n**11n)), 2n**65n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**13n + 2n**12n - 1n)), 2n**65n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**13n + 2n**12n)), 2n**65n + 2n**14n);
+assertEq(BigInt(Number(2n**65n + 2n**13n + 2n**12n + 1n)), 2n**65n + 2n**14n);
+
+// ...and in these tests, the contributed bit from the most-significant digit
+// is additionally nonzero.
+assertEq(BigInt(Number(2n**65n + 2n**64n)), 2n**65n + 2n**64n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 1n)), 2n**65n + 2n**64n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**5n)), 2n**65n + 2n**64n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**11n)), 2n**65n + 2n**64n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**12n)), 2n**65n + 2n**64n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**12n + 1n)), 2n**65n + 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n)), 2n**65n + 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n + 1n)), 2n**65n + 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n + 2n**5n)), 2n**65n + 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n + 2n**11n)), 2n**65n + 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n + 2n**12n - 1n)), 2n**65n + 2n**64n + 2n**13n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n + 2n**12n)), 2n**65n + 2n**64n + 2n**14n);
+assertEq(BigInt(Number(2n**65n + 2n**64n + 2n**13n + 2n**12n + 1n)), 2n**65n + 2n**64n + 2n**14n);
+
+/**
+ * Versions of the testing above with all the high-order bits massively bumped
+ * upward to test that super-low bits, not just bits in high digits, are
+ * properly accounted for in rounding.
+ */
+
+// The tests below that aren't subtracting bits will have no bits in the
+// ultimate significand from the most-significant digit (because of the implicit
+// one being excluded).
+assertEq(BigInt(Number(2n**940n - 2n**887n + 1n)), 2n**940n - 2n**887n);
+assertEq(BigInt(Number(2n**940n - 2n**887n + 2n**886n - 1n)), 2n**940n - 2n**887n);
+assertEq(BigInt(Number(2n**940n - 2n**887n + 2n**886n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n - 2n**886n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n + 1n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n + 2n**880n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n + 2n**885n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n + 2n**887n)), 2n**940n);
+assertEq(BigInt(Number(2n**940n + 2n**887n + 1n)), 2n**940n + 2n**888n);
+assertEq(BigInt(Number(2n**940n + 2n**888n)), 2n**940n + 2n**888n);
+assertEq(BigInt(Number(2n**940n + 2n**888n + 1n)), 2n**940n + 2n**888n);
+assertEq(BigInt(Number(2n**940n + 2n**888n + 2n**5n)), 2n**940n + 2n**888n);
+assertEq(BigInt(Number(2n**940n + 2n**888n + 2n**12n)), 2n**940n + 2n**888n);
+assertEq(BigInt(Number(2n**940n + 2n**888n + 2n**887n - 1n)), 2n**940n + 2n**888n);
+assertEq(BigInt(Number(2n**940n + 2n**888n + 2n**887n)), 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**940n + 2n**888n + 2n**887n + 1n)), 2n**940n + 2n**889n);
+
+// These tests *will* have a bit from the most-significant digit in the ultimate
+// significand.
+assertEq(BigInt(Number(2n**941n - 2n**888n)), 2n**941n - 2n**888n);
+assertEq(BigInt(Number(2n**941n - 2n**888n + 2n**887n - 1n)), 2n**941n - 2n**888n);
+assertEq(BigInt(Number(2n**941n - 2n**888n + 2n**887n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n - 2n**887n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n + 1n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n + 2n**881n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n + 2n**886n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n + 2n**888n)), 2n**941n);
+assertEq(BigInt(Number(2n**941n + 2n**888n + 1n)), 2n**941n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**889n)), 2n**941n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**889n + 1n)), 2n**941n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**889n + 2n**5n)), 2n**941n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**889n + 2n**12n)), 2n**941n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**889n + 2n**888n - 1n)), 2n**941n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**889n + 2n**888n)), 2n**941n + 2n**890n);
+assertEq(BigInt(Number(2n**941n + 2n**889n + 2n**888n + 1n)), 2n**941n + 2n**890n);
+
+// ...and in these tests, the contributed bit from the most-significant digit
+// is additionally nonzero.
+assertEq(BigInt(Number(2n**941n + 2n**940n)), 2n**941n + 2n**940n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 1n)), 2n**941n + 2n**940n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**881n)), 2n**941n + 2n**940n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**886n)), 2n**941n + 2n**940n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**888n)), 2n**941n + 2n**940n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**888n + 1n)), 2n**941n + 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n)), 2n**941n + 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n + 1n)), 2n**941n + 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n + 2n**5n)), 2n**941n + 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n + 2n**12n)), 2n**941n + 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n + 2n**888n - 1n)), 2n**941n + 2n**940n + 2n**889n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n + 2n**888n)), 2n**941n + 2n**940n + 2n**890n);
+assertEq(BigInt(Number(2n**941n + 2n**940n + 2n**889n + 2n**888n + 1n)), 2n**941n + 2n**940n + 2n**890n);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
--- a/js/src/vm/BigIntType.cpp
+++ b/js/src/vm/BigIntType.cpp
@@ -2721,18 +2721,19 @@ double BigInt::numberValue(BigInt* x) {
   constexpr uint8_t ExponentShift = Double::kExponentShift;
   constexpr uint8_t SignificandWidth = Double::kSignificandWidth;
   constexpr unsigned ExponentBias = Double::kExponentBias;
   constexpr uint8_t SignShift = Double::kExponentWidth + SignificandWidth;
 
   size_t length = x->digitLength();
   MOZ_ASSERT(length != 0);
 
-  // Fast path for the likely-common case of up to a uint64_t of magnitude
-  // that doesn't exceed integral precision in IEEE-754.
+  // Fast path for the likely-common case of up to a uint64_t of magnitude not
+  // exceeding integral precision in IEEE-754.  (Note that we *depend* on this
+  // optimization being performed further down.)
   if (length <= 64 / DigitBits) {
     uint64_t magnitude = x->digit(0);
     if (DigitBits == 32 && length > 1) {
       magnitude |= uint64_t(x->digit(1)) << 32;
     }
     const uint64_t MaxIntegralPrecisionDouble = uint64_t(1)
                                                 << (SignificandWidth + 1);
     if (magnitude <= MaxIntegralPrecisionDouble) {
@@ -2755,72 +2756,175 @@ double BigInt::numberValue(BigInt* x) {
   // Otherwise munge the most significant bits of the number into proper
   // position in an IEEE-754 double and go to town.
 
   // Omit the most significant bit: the IEEE-754 format includes this bit
   // implicitly for all double-precision integers.
   const uint8_t msdIgnoredBits = msdLeadingZeroes + 1;
   const uint8_t msdIncludedBits = DigitBits - msdIgnoredBits;
 
-  uint8_t bitsFilled = msdIncludedBits;
-
-  // Shift `msd`'s contributed bits upward to remove high-order zeroes and
-  // the highest set bit (which is implicit in IEEE-754 integral values so
-  // must be removed) and to add low-order zeroes.
+  // We compute the final mantissa of the result, shifted upward to the top of
+  // the `uint64_t` space -- plus an extra bit to detect potential rounding.
+  constexpr uint8_t BitsNeededForShiftedMantissa = SignificandWidth + 1;
+
+  // Shift `msd`'s contributed bits upward to remove high-order zeroes and the
+  // highest set bit (which is implicit in IEEE-754 integral values so must be
+  // removed) and to add low-order zeroes.  (Lower-order garbage bits are
+  // discarded when `shiftedMantissa` is converted to a real mantissa.)
   uint64_t shiftedMantissa =
       msdIncludedBits == 0 ? 0 : uint64_t(msd) << (64 - msdIncludedBits);
 
-  // Add in bits from the next one or two digits if `msd` didn't contain all
-  // bits necessary to define the result.  (The extra bit allows us to
-  // properly round an inexact overall result.)  Any lower bits that are
-  // uselessly set will be shifted away when `shiftedMantissa` is converted to
-  // a real mantissa.
-  if (bitsFilled < SignificandWidth + 1) {
+  // If the extra bit is set, correctly rounding the result may require
+  // examining all lower-order bits.  Also compute 1) the index of the Digit
+  // storing the extra bit, and 2) whether bits beneath the extra bit in that
+  // Digit are nonzero so we can round if needed.
+  size_t digitContainingExtraBit;
+  Digit bitsBeneathExtraBitInDigitContainingExtraBit;
+
+  // Add shifted bits to `shiftedMantissa` until we have a complete mantissa and
+  // an extra bit.
+  if (msdIncludedBits >= BitsNeededForShiftedMantissa) {
+    //       DigitBits=64 (necessarily for msdIncludedBits ≥ SignificandWidth+1;
+    //            |        C++ compiler range analysis ought eliminate this
+    //            |        check on 32-bit)
+    //   _________|__________
+    //  /                    \
+    //        msdIncludedBits
+    //      ________|________
+    //     /                 \
+    // [001···················|
+    //  \_/\_____________/\__/
+    //   |            |    |
+    // msdIgnoredBits |   bits below the extra bit (may be no bits)
+    //      BitsNeededForShiftedMantissa=SignificandWidth+1
+    digitContainingExtraBit = length - 1;
+
+    const uint8_t countOfBitsInDigitBelowExtraBit =
+        DigitBits - BitsNeededForShiftedMantissa - msdIgnoredBits;
+    bitsBeneathExtraBitInDigitContainingExtraBit =
+        msd & ((Digit(1) << CountOfBitsInDigitBelowExtraBit) - 1);
+  } else {
     MOZ_ASSERT(length >= 2,
                "single-Digit numbers with this few bits should have been "
                "handled by the fast-path above");
 
     Digit second = x->digit(length - 2);
-    if (DigitBits == 32) {
+    if (DigitBits == 64) {
+      shiftedMantissa |= second >> msdIncludedBits;
+
+      digitContainingExtraBit = length - 2;
+
+      //  msdIncludedBits + DigitBits
+      //      ________|_________
+      //     /                  \
+      //             DigitBits=64
+      // msdIncludedBits    |
+      //      __|___   _____|___
+      //     /      \ /         \
+      // [001········|···········|
+      //  \_/\_____________/\___/
+      //   |            |     |
+      // msdIgnoredBits | bits below the extra bit (always more than one)
+      //                |
+      //      BitsNeededForShiftedMantissa=SignificandWidth+1
+      const uint8_t countOfBitsInSecondDigitBelowExtraBit =
+          (msdIncludedBits + DigitBits) - BitsNeededForShiftedMantissa;
+
+      bitsBeneathExtraBitInDigitContainingExtraBit =
+          second << (DigitBits - CountOfBitsInSecondDigitBelowExtraBit);
+    } else {
       shiftedMantissa |= uint64_t(second) << msdIgnoredBits;
-      bitsFilled += DigitBits;
-
-      // Add in bits from another digit, if any, if we still have unfilled
-      // significand bits.
-      if (bitsFilled < SignificandWidth + 1 && length >= 3) {
+
+      if (msdIncludedBits + DigitBits >= BitsNeededForShiftedMantissa) {
+        digitContainingExtraBit = length - 2;
+
+        //  msdIncludedBits + DigitBits
+        //      ______|________
+        //     /               \
+        //             DigitBits=32
+        // msdIncludedBits |
+        //      _|_   _____|___
+        //     /   \ /         \
+        // [001·····|···········|
+        //     \___________/\__/
+        //          |        |
+        //          |      bits below the extra bit (may be no bits)
+        //      BitsNeededForShiftedMantissa=SignificandWidth+1
+        const uint8_t countOfBitsInSecondDigitBelowExtraBit =
+            (msdIncludedBits + DigitBits) - BitsNeededForShiftedMantissa;
+
+        bitsBeneathExtraBitInDigitContainingExtraBit =
+            second & ((Digit(1) << CountOfBitsInSecondDigitBelowExtraBit) - 1);
+      } else {
+        MOZ_ASSERT(length >= 3,
+                   "we must have at least three digits here, because "
+                   "`msdIncludedBits + 32 < BitsNeededForShiftedMantissa` "
+                   "guarantees `x < 2**53` -- and therefore the "
+                   "MaxIntegralPrecisionDouble optimization above will have "
+                   "handled two-digit cases");
+
         Digit third = x->digit(length - 3);
         shiftedMantissa |= uint64_t(third) >> msdIncludedBits;
-        // The second and third 32-bit digits contributed 64 bits total, filling
-        // well beyond the mantissa.
-        bitsFilled = 64;
+
+        digitContainingExtraBit = length - 3;
+
+        //    msdIncludedBits + DigitBits + DigitBits
+        //      ____________|______________
+        //     /                           \
+        //             DigitBits=32
+        // msdIncludedBits |     DigitBits=32
+        //      _|_   _____|___   ____|____
+        //     /   \ /         \ /         \
+        // [001·····|···········|···········|
+        //     \____________________/\_____/
+        //               |               |
+        //               |      bits below the extra bit
+        //      BitsNeededForShiftedMantissa=SignificandWidth+1
+        static_assert(2 * DigitBits > BitsNeededForShiftedMantissa,
+                      "two 32-bit digits should more than fill a mantissa");
+        const uint8_t countOfBitsInThirdDigitBelowExtraBit =
+            msdIncludedBits + 2 * DigitBits - BitsNeededForShiftedMantissa;
+
+        // Shift out the mantissa bits and the extra bit.
+        bitsBeneathExtraBitInDigitContainingExtraBit =
+            third << (DigitBits - CountOfBitsInThirdDigitBelowExtraBit);
       }
-    } else {
-      shiftedMantissa |= second >> msdIncludedBits;
-      // A full 64-bit digit's worth of bits (some from the most significant
-      // digit, the rest from the next) fills well beyond the mantissa.
-      bitsFilled = 64;
     }
   }
 
-  // Round the overall result, if necessary.  (It's possible we don't need to
-  // round -- the number might not have enough bits to round.)
-  if (bitsFilled >= SignificandWidth + 1) {
-    constexpr uint64_t LeastSignificantBit = uint64_t(1)
-                                             << (64 - SignificandWidth);
-    constexpr uint64_t ExtraBit = LeastSignificantBit >> 1;
-
-    // When the first bit outside the significand is set, the overall value
-    // is rounded: downward (i.e. no change to the bits) if the least
-    // significant bit in the significand is zero, upward if it instead is
-    // one.
-    if ((shiftedMantissa & ExtraBit) &&
-        (shiftedMantissa & LeastSignificantBit)) {
-      // We're rounding upward: add to the significand bits.  If they
-      // overflow, the exponent must also be increased.  If *that*
-      // overflows, return the appropriate infinity.
+  constexpr uint64_t LeastSignificantBit = uint64_t(1)
+                                           << (64 - SignificandWidth);
+  constexpr uint64_t ExtraBit = LeastSignificantBit >> 1;
+
+  // The extra bit must be set for rounding to change the mantissa.
+  if ((shiftedMantissa & ExtraBit) != 0) {
+    bool shouldRoundUp;
+    if (shiftedMantissa & LeastSignificantBit) {
+      // If the lowest mantissa bit is set, it doesn't matter what lower bits
+      // are: nearest-even rounds up regardless.
+      shouldRoundUp = true;
+    } else {
+      // If the lowest mantissa bit is unset, *all* lower bits are relevant.
+      // All-zero bits below the extra bit situates `x` halfway between two
+      // values, and the nearest *even* value lies downward.  But if any bit
+      // below the extra bit is set, `x` is closer to the rounded-up value.
+      shouldRoundUp = bitsBeneathExtraBitInDigitContainingExtraBit != 0;
+      if (!shouldRoundUp) {
+        while (digitContainingExtraBit-- > 0) {
+          if (x->digit(digitContainingExtraBit) != 0) {
+            shouldRoundUp = true;
+            break;
+          }
+        }
+      }
+    }
+
+    if (shouldRoundUp) {
+      // Add one to the significand bits.  If they overflow, the exponent must
+      // also be increased.  If *that* overflows, return the correct infinity.
       uint64_t before = shiftedMantissa;
       shiftedMantissa += ExtraBit;
       if (shiftedMantissa < before) {
         exponent++;
         if (exponent > ExponentBias) {
           return x->isNegative() ? NegativeInfinity<double>()
                                  : PositiveInfinity<double>();
         }