summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/Int128.cpp
blob: e712a64be603fb399003c8e67f93dbf368a9fa61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "builtin/temporal/Int128.h"

#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"

#include <stdint.h>

using namespace js;
using namespace js::temporal;

double Uint128::toDouble(const Uint128& x, bool negative) {
  // Simplified version of |BigInt::numberValue()| for DigitBits=64. See the
  // comments in |BigInt::numberValue()| for how this code works.

  using Double = mozilla::FloatingPoint<double>;
  constexpr uint8_t ExponentShift = Double::kExponentShift;
  constexpr uint8_t SignificandWidth = Double::kSignificandWidth;
  constexpr unsigned ExponentBias = Double::kExponentBias;
  constexpr uint8_t SignShift = Double::kExponentWidth + SignificandWidth;

  constexpr uint64_t MaxIntegralPrecisionDouble = uint64_t(1)
                                                  << (SignificandWidth + 1);

  // 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;

  uint64_t shiftedMantissa = 0;
  uint64_t exponent = 0;

  // 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.
  uint64_t bitsBeneathExtraBitInDigitContainingExtraBit = 0;

  if (x.high == 0) {
    uint64_t msd = x.low;

    // Fast path for the likely-common case of up to a uint64_t of magnitude not
    // exceeding integral precision in IEEE-754.
    if (msd <= MaxIntegralPrecisionDouble) {
      return negative ? -double(msd) : +double(msd);
    }

    const uint8_t msdLeadingZeroes = mozilla::CountLeadingZeroes64(msd);
    MOZ_ASSERT(0 <= msdLeadingZeroes && msdLeadingZeroes <= 10,
               "leading zeroes is at most 10 when the fast path isn't taken");

    exponent = 64 - msdLeadingZeroes - 1;

    // Omit the most significant bit: the IEEE-754 format includes this bit
    // implicitly for all double-precision integers.
    const uint8_t msdIgnoredBits = msdLeadingZeroes + 1;
    MOZ_ASSERT(1 <= msdIgnoredBits && msdIgnoredBits <= 11);

    const uint8_t msdIncludedBits = 64 - msdIgnoredBits;
    MOZ_ASSERT(53 <= msdIncludedBits && msdIncludedBits <= 63);
    MOZ_ASSERT(msdIncludedBits >= BitsNeededForShiftedMantissa);

    // Munge the most significant bits of the number into proper
    // position in an IEEE-754 double and go to town.

    // 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.)
    shiftedMantissa = msd << (64 - msdIncludedBits);

    // Add shifted bits to `shiftedMantissa` until we have a complete mantissa
    // and an extra bit.
    const uint8_t countOfBitsInDigitBelowExtraBit =
        64 - BitsNeededForShiftedMantissa - msdIgnoredBits;
    bitsBeneathExtraBitInDigitContainingExtraBit =
        msd & ((uint64_t(1) << countOfBitsInDigitBelowExtraBit) - 1);
  } else {
    uint64_t msd = x.high;
    uint64_t second = x.low;

    uint8_t msdLeadingZeroes = mozilla::CountLeadingZeroes64(msd);

    exponent = 2 * 64 - msdLeadingZeroes - 1;

    // 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 = 64 - msdIgnoredBits;

    // 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.)
    shiftedMantissa = msdIncludedBits == 0 ? 0 : msd << (64 - msdIncludedBits);

    // Add shifted bits to `shiftedMantissa` until we have a complete mantissa
    // and an extra bit.
    if (msdIncludedBits >= BitsNeededForShiftedMantissa) {
      const uint8_t countOfBitsInDigitBelowExtraBit =
          64 - BitsNeededForShiftedMantissa - msdIgnoredBits;
      bitsBeneathExtraBitInDigitContainingExtraBit =
          msd & ((uint64_t(1) << countOfBitsInDigitBelowExtraBit) - 1);

      if (bitsBeneathExtraBitInDigitContainingExtraBit == 0) {
        bitsBeneathExtraBitInDigitContainingExtraBit = second;
      }
    } else {
      shiftedMantissa |= second >> msdIncludedBits;

      const uint8_t countOfBitsInSecondDigitBelowExtraBit =
          (msdIncludedBits + 64) - BitsNeededForShiftedMantissa;
      bitsBeneathExtraBitInDigitContainingExtraBit =
          second << (64 - countOfBitsInSecondDigitBelowExtraBit);
    }
  }

  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) {
      // 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++;
      }
    }
  }

  uint64_t significandBits = shiftedMantissa >> (64 - SignificandWidth);
  uint64_t signBit = uint64_t(negative ? 1 : 0) << SignShift;
  uint64_t exponentBits = (exponent + ExponentBias) << ExponentShift;
  return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits);
}