зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1874684 - Part 1: Add Int128 and Uint128 types. r=dminor
Division operations in Temporal will soon be limited to int128 values. Introduce Int128 and Uint128 to avoid `BigInt` allocations for these divisions. Int128 and Uint128 are both implemented as a pair of `uint64_t` values. All basic algorithmic operations are supported. Non-trivial operations were implemented using the algorithms described in Hacker's Delight, except for `Uint128::toDouble()` which was implemented by copying and then adjusting the code for `BigInt::numberValue()`. Differential Revision: https://phabricator.services.mozilla.com/D198534
This commit is contained in:
Родитель
880e45e3ae
Коммит
6920618be1
|
@ -0,0 +1,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);
|
||||
}
|
|
@ -0,0 +1,742 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef builtin_temporal_Int128_h
|
||||
#define builtin_temporal_Int128_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
|
||||
#include <climits>
|
||||
#include <limits>
|
||||
#include <stdint.h>
|
||||
#include <utility>
|
||||
|
||||
namespace js::temporal {
|
||||
|
||||
class Int128;
|
||||
class Uint128;
|
||||
|
||||
/**
|
||||
* Unsigned 128-bit integer, implemented as a pair of unsigned 64-bit integers.
|
||||
*
|
||||
* Supports all basic arithmetic operators.
|
||||
*/
|
||||
class alignas(16) Uint128 final {
|
||||
#if MOZ_LITTLE_ENDIAN()
|
||||
uint64_t low = 0;
|
||||
uint64_t high = 0;
|
||||
#else
|
||||
uint64_t high = 0;
|
||||
uint64_t low = 0;
|
||||
#endif
|
||||
|
||||
friend class Int128;
|
||||
|
||||
constexpr Uint128(uint64_t low, uint64_t high) : low(low), high(high) {}
|
||||
|
||||
/**
|
||||
* Return the high double-word of the multiplication of `u * v`.
|
||||
*
|
||||
* Based on "Multiply high unsigned" from Hacker's Delight, 2nd edition,
|
||||
* figure 8-2.
|
||||
*/
|
||||
static constexpr uint64_t mulhu(uint64_t u, uint64_t v) {
|
||||
uint64_t u0 = u & 0xffff'ffff;
|
||||
uint64_t u1 = u >> 32;
|
||||
|
||||
uint64_t v0 = v & 0xffff'ffff;
|
||||
uint64_t v1 = v >> 32;
|
||||
|
||||
uint64_t w0 = u0 * v0;
|
||||
uint64_t t = u1 * v0 + (w0 >> 32);
|
||||
uint64_t w1 = t & 0xffff'ffff;
|
||||
uint64_t w2 = t >> 32;
|
||||
w1 = u0 * v1 + w1;
|
||||
return u1 * v1 + w2 + (w1 >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on "Unsigned doubleword division from long division" from
|
||||
* Hacker's Delight, 2nd edition, figure 9-5.
|
||||
*/
|
||||
static constexpr std::pair<Uint128, Uint128> udivdi(const Uint128& u,
|
||||
const Uint128& v) {
|
||||
MOZ_ASSERT(v != Uint128{});
|
||||
|
||||
// If v < 2**64
|
||||
if (v.high == 0) {
|
||||
// If u < 2**64
|
||||
if (u.high == 0) {
|
||||
// Prefer built-in division if possible.
|
||||
return {Uint128{u.low / v.low, 0}, Uint128{u.low % v.low, 0}};
|
||||
}
|
||||
|
||||
// If u/v cannot overflow, just do one division.
|
||||
if (Uint128{u.high, 0} < v) {
|
||||
auto [q, r] = divlu(u.high, u.low, v.low);
|
||||
return {Uint128{q, 0}, Uint128{r, 0}};
|
||||
}
|
||||
|
||||
// If u/v would overflow: Break u up into two halves.
|
||||
|
||||
// First quotient digit and first remainder, < v.
|
||||
auto [q1, r1] = divlu(0, u.high, v.low);
|
||||
|
||||
// Second quotient digit.
|
||||
auto [q0, r0] = divlu(r1, u.low, v.low);
|
||||
|
||||
// Return quotient and remainder.
|
||||
return {Uint128{q0, q1}, Uint128{r0, 0}};
|
||||
}
|
||||
|
||||
// Here v >= 2**64.
|
||||
|
||||
// 0 <= n <= 63
|
||||
auto n = mozilla::CountLeadingZeroes64(v.high);
|
||||
|
||||
// Normalize the divisor so its MSB is 1.
|
||||
auto v1 = (v << n).high;
|
||||
|
||||
// To ensure no overflow.
|
||||
auto u1 = u >> 1;
|
||||
|
||||
// Get quotient from divide unsigned instruction.
|
||||
auto [q1, r1] = divlu(u1.high, u1.low, v1);
|
||||
|
||||
// Undo normalization and division of u by 2.
|
||||
auto q0 = (Uint128{q1, 0} << n) >> 63;
|
||||
|
||||
// Make q0 correct or too small by 1.
|
||||
if (q0 != Uint128{0}) {
|
||||
q0 -= Uint128{1};
|
||||
}
|
||||
|
||||
// Now q0 is correct.
|
||||
auto r0 = u - q0 * v;
|
||||
if (r0 >= v) {
|
||||
q0 += Uint128{1};
|
||||
r0 -= v;
|
||||
}
|
||||
|
||||
// Return quotient and remainder.
|
||||
return {q0, r0};
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on "Divide long unsigned, using fullword division instructions" from
|
||||
* Hacker's Delight, 2nd edition, figure 9-3.
|
||||
*/
|
||||
static constexpr std::pair<uint64_t, uint64_t> divlu(uint64_t u1, uint64_t u0,
|
||||
uint64_t v) {
|
||||
// Number base (32 bits).
|
||||
constexpr uint64_t base = 4294967296;
|
||||
|
||||
// If overflow, set the remainder to an impossible value and return the
|
||||
// largest possible quotient.
|
||||
if (u1 >= v) {
|
||||
return {UINT64_MAX, UINT64_MAX};
|
||||
}
|
||||
|
||||
// Shift amount for normalization. (0 <= s <= 63)
|
||||
int64_t s = mozilla::CountLeadingZeroes64(v);
|
||||
|
||||
// Normalize the divisor.
|
||||
v = v << s;
|
||||
|
||||
// Normalized divisor digits.
|
||||
//
|
||||
// Break divisor up into two 32-bit digits.
|
||||
uint64_t vn1 = v >> 32;
|
||||
uint64_t vn0 = uint32_t(v);
|
||||
|
||||
// Dividend digit pairs.
|
||||
//
|
||||
// Shift dividend left.
|
||||
uint64_t un32 = (u1 << s) | ((u0 >> ((64 - s) & 63)) & (-s >> 63));
|
||||
uint64_t un10 = u0 << s;
|
||||
|
||||
// Normalized dividend least significant digits.
|
||||
//
|
||||
// Break right half of dividend into two digits.
|
||||
uint64_t un1 = un10 >> 32;
|
||||
uint64_t un0 = uint32_t(un10);
|
||||
|
||||
// Compute the first quotient digit and its remainder.
|
||||
uint64_t q1 = un32 / vn1;
|
||||
uint64_t rhat = un32 - q1 * vn1;
|
||||
while (q1 >= base || q1 * vn0 > base * rhat + un1) {
|
||||
q1 -= 1;
|
||||
rhat += vn1;
|
||||
if (rhat >= base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply and subtract.
|
||||
uint64_t un21 = un32 * base + un1 - q1 * v;
|
||||
|
||||
// Compute the second quotient digit and its remainder.
|
||||
uint64_t q0 = un21 / vn1;
|
||||
rhat = un21 - q0 * vn1;
|
||||
while (q0 >= base || q0 * vn0 > base * rhat + un0) {
|
||||
q0 -= 1;
|
||||
rhat += vn1;
|
||||
if (rhat >= base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the quotient and remainder.
|
||||
uint64_t q = q1 * base + q0;
|
||||
uint64_t r = (un21 * base + un0 - q0 * v) >> s;
|
||||
return {q, r};
|
||||
}
|
||||
|
||||
static double toDouble(const Uint128& x, bool negative);
|
||||
|
||||
public:
|
||||
constexpr Uint128() = default;
|
||||
constexpr Uint128(const Uint128&) = default;
|
||||
|
||||
explicit constexpr Uint128(uint64_t value)
|
||||
: Uint128(uint64_t(value), uint64_t(0)) {}
|
||||
|
||||
constexpr bool operator==(const Uint128& other) const {
|
||||
return low == other.low && high == other.high;
|
||||
}
|
||||
|
||||
constexpr bool operator<(const Uint128& other) const {
|
||||
if (high == other.high) {
|
||||
return low < other.low;
|
||||
}
|
||||
return high < other.high;
|
||||
}
|
||||
|
||||
// Other operators are implemented in terms of operator== and operator<.
|
||||
constexpr bool operator!=(const Uint128& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
constexpr bool operator>(const Uint128& other) const { return other < *this; }
|
||||
constexpr bool operator<=(const Uint128& other) const {
|
||||
return !(other < *this);
|
||||
}
|
||||
constexpr bool operator>=(const Uint128& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const { return !(*this == Uint128{}); }
|
||||
|
||||
explicit constexpr operator int8_t() const { return int8_t(low); }
|
||||
explicit constexpr operator int16_t() const { return int16_t(low); }
|
||||
explicit constexpr operator int32_t() const { return int32_t(low); }
|
||||
explicit constexpr operator int64_t() const { return int64_t(low); }
|
||||
|
||||
explicit constexpr operator uint8_t() const { return uint8_t(low); }
|
||||
explicit constexpr operator uint16_t() const { return uint16_t(low); }
|
||||
explicit constexpr operator uint32_t() const { return uint32_t(low); }
|
||||
explicit constexpr operator uint64_t() const { return uint64_t(low); }
|
||||
|
||||
explicit constexpr operator Int128() const;
|
||||
|
||||
explicit operator double() const { return toDouble(*this, false); }
|
||||
|
||||
constexpr Uint128 operator+(const Uint128& other) const {
|
||||
// "§2-16 Double-Length Add/Subtract" from Hacker's Delight, 2nd edition.
|
||||
Uint128 result;
|
||||
result.low = low + other.low;
|
||||
result.high = high + other.high + static_cast<uint64_t>(result.low < low);
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator-(const Uint128& other) const {
|
||||
// "§2-16 Double-Length Add/Subtract" from Hacker's Delight, 2nd edition.
|
||||
Uint128 result;
|
||||
result.low = low - other.low;
|
||||
result.high = high - other.high - static_cast<uint64_t>(low < other.low);
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator*(const Uint128& other) const {
|
||||
uint64_t w01 = low * other.high;
|
||||
uint64_t w10 = high * other.low;
|
||||
uint64_t w00 = mulhu(low, other.low);
|
||||
|
||||
uint64_t w1 = w00 + w10 + w01;
|
||||
uint64_t w0 = low * other.low;
|
||||
|
||||
return Uint128{w0, w1};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the quotient and remainder of the division.
|
||||
*/
|
||||
constexpr std::pair<Uint128, Uint128> divrem(const Uint128& divisor) const {
|
||||
return udivdi(*this, divisor);
|
||||
}
|
||||
|
||||
constexpr Uint128 operator/(const Uint128& other) const {
|
||||
auto [quot, rem] = divrem(other);
|
||||
return quot;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator%(const Uint128& other) const {
|
||||
auto [quot, rem] = divrem(other);
|
||||
return rem;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator<<(int shift) const {
|
||||
MOZ_ASSERT(0 <= shift && shift <= 127, "undefined shift amount");
|
||||
|
||||
// Ensure the shift amount is in range.
|
||||
shift &= 127;
|
||||
|
||||
// "§2-17 Double-Length Shifts" from Hacker's Delight, 2nd edition.
|
||||
if (shift >= 64) {
|
||||
uint64_t y0 = 0;
|
||||
uint64_t y1 = low << (shift - 64);
|
||||
return Uint128{y0, y1};
|
||||
}
|
||||
uint64_t y0 = low << shift;
|
||||
uint64_t y1 = (high << shift) | (low >> (64 - shift));
|
||||
return Uint128{y0, y1};
|
||||
}
|
||||
|
||||
constexpr Uint128 operator>>(int shift) const {
|
||||
MOZ_ASSERT(0 <= shift && shift <= 127, "undefined shift amount");
|
||||
|
||||
// Ensure the shift amount is in range.
|
||||
shift &= 127;
|
||||
|
||||
// "§2-17 Double-Length Shifts" from Hacker's Delight, 2nd edition.
|
||||
if (shift >= 64) {
|
||||
uint64_t y0 = high >> (shift - 64);
|
||||
uint64_t y1 = 0;
|
||||
return Uint128{y0, y1};
|
||||
}
|
||||
uint64_t y0 = low >> shift | high << (64 - shift);
|
||||
uint64_t y1 = high >> shift;
|
||||
return Uint128{y0, y1};
|
||||
}
|
||||
|
||||
constexpr Uint128 operator&(const Uint128& other) const {
|
||||
return Uint128{low & other.low, high & other.high};
|
||||
}
|
||||
|
||||
constexpr Uint128 operator|(const Uint128& other) const {
|
||||
return Uint128{low | other.low, high | other.high};
|
||||
}
|
||||
|
||||
constexpr Uint128 operator^(const Uint128& other) const {
|
||||
return Uint128{low ^ other.low, high ^ other.high};
|
||||
}
|
||||
|
||||
constexpr Uint128 operator~() const { return Uint128{~low, ~high}; }
|
||||
|
||||
constexpr Uint128 operator-() const { return Uint128{} - *this; }
|
||||
|
||||
constexpr Uint128 operator+() const { return *this; }
|
||||
|
||||
constexpr Uint128& operator++() {
|
||||
*this = *this + Uint128{1, 0};
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator++(int) {
|
||||
auto result = *this;
|
||||
++*this;
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr Uint128& operator--() {
|
||||
*this = *this - Uint128{1, 0};
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator--(int) {
|
||||
auto result = *this;
|
||||
--*this;
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator+=(const Uint128& other) {
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator-=(const Uint128& other) {
|
||||
*this = *this - other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator*=(const Uint128& other) {
|
||||
*this = *this * other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator/=(const Uint128& other) {
|
||||
*this = *this / other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator%=(const Uint128& other) {
|
||||
*this = *this % other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator&=(const Uint128& other) {
|
||||
*this = *this & other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator|=(const Uint128& other) {
|
||||
*this = *this | other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator^=(const Uint128& other) {
|
||||
*this = *this ^ other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator<<=(int shift) {
|
||||
*this = *this << shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Uint128 operator>>=(int shift) {
|
||||
*this = *this >> shift;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Signed 128-bit integer, implemented as a pair of unsigned 64-bit integers.
|
||||
*
|
||||
* Supports all basic arithmetic operators.
|
||||
*/
|
||||
class alignas(16) Int128 final {
|
||||
#if MOZ_LITTLE_ENDIAN()
|
||||
uint64_t low = 0;
|
||||
uint64_t high = 0;
|
||||
#else
|
||||
uint64_t high = 0;
|
||||
uint64_t low = 0;
|
||||
#endif
|
||||
|
||||
friend class Uint128;
|
||||
|
||||
constexpr Int128(uint64_t low, uint64_t high) : low(low), high(high) {}
|
||||
|
||||
/**
|
||||
* Based on "Signed doubleword division from unsigned doubleword division"
|
||||
* from Hacker's Delight, 2nd edition, figure 9-6.
|
||||
*/
|
||||
static constexpr std::pair<Int128, Int128> divdi(const Int128& u,
|
||||
const Int128& v) {
|
||||
auto [q, r] = Uint128::udivdi(u.abs(), v.abs());
|
||||
|
||||
// If u and v have different signs, negate q.
|
||||
// If is negative, negate r.
|
||||
auto t = static_cast<Uint128>((u ^ v) >> 127);
|
||||
auto s = static_cast<Uint128>(u >> 127);
|
||||
return {static_cast<Int128>((q ^ t) - t), static_cast<Int128>((r ^ s) - s)};
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr Int128() = default;
|
||||
constexpr Int128(const Int128&) = default;
|
||||
|
||||
explicit constexpr Int128(int64_t value)
|
||||
: Int128(uint64_t(value), uint64_t(value >> 63)) {}
|
||||
|
||||
/**
|
||||
* Return the quotient and remainder of the division.
|
||||
*/
|
||||
constexpr std::pair<Int128, Int128> divrem(const Int128& divisor) const {
|
||||
return divdi(*this, divisor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute value of this integer.
|
||||
*/
|
||||
constexpr Uint128 abs() const {
|
||||
if (*this >= Int128{}) {
|
||||
return Uint128{low, high};
|
||||
}
|
||||
auto neg = -*this;
|
||||
return Uint128{neg.low, neg.high};
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Int128& other) const {
|
||||
return low == other.low && high == other.high;
|
||||
}
|
||||
|
||||
constexpr bool operator<(const Int128& other) const {
|
||||
if (high == other.high) {
|
||||
return low < other.low;
|
||||
}
|
||||
return int64_t(high) < int64_t(other.high);
|
||||
}
|
||||
|
||||
// Other operators are implemented in terms of operator== and operator<.
|
||||
constexpr bool operator!=(const Int128& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
constexpr bool operator>(const Int128& other) const { return other < *this; }
|
||||
constexpr bool operator<=(const Int128& other) const {
|
||||
return !(other < *this);
|
||||
}
|
||||
constexpr bool operator>=(const Int128& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const { return !(*this == Int128{}); }
|
||||
|
||||
explicit constexpr operator int8_t() const { return int8_t(low); }
|
||||
explicit constexpr operator int16_t() const { return int16_t(low); }
|
||||
explicit constexpr operator int32_t() const { return int32_t(low); }
|
||||
explicit constexpr operator int64_t() const { return int64_t(low); }
|
||||
|
||||
explicit constexpr operator uint8_t() const { return uint8_t(low); }
|
||||
explicit constexpr operator uint16_t() const { return uint16_t(low); }
|
||||
explicit constexpr operator uint32_t() const { return uint32_t(low); }
|
||||
explicit constexpr operator uint64_t() const { return uint64_t(low); }
|
||||
|
||||
explicit constexpr operator Uint128() const { return Uint128{low, high}; }
|
||||
|
||||
explicit operator double() const {
|
||||
return Uint128::toDouble(abs(), *this < Int128{0});
|
||||
}
|
||||
|
||||
constexpr Int128 operator+(const Int128& other) const {
|
||||
return Int128{Uint128{*this} + Uint128{other}};
|
||||
}
|
||||
|
||||
constexpr Int128 operator-(const Int128& other) const {
|
||||
return Int128{Uint128{*this} - Uint128{other}};
|
||||
}
|
||||
|
||||
constexpr Int128 operator*(const Int128& other) const {
|
||||
return Int128{Uint128{*this} * Uint128{other}};
|
||||
}
|
||||
|
||||
constexpr Int128 operator/(const Int128& other) const {
|
||||
auto [quot, rem] = divrem(other);
|
||||
return quot;
|
||||
}
|
||||
|
||||
constexpr Int128 operator%(const Int128& other) const {
|
||||
auto [quot, rem] = divrem(other);
|
||||
return rem;
|
||||
}
|
||||
|
||||
constexpr Int128 operator<<(int shift) const {
|
||||
return Int128{Uint128{*this} << shift};
|
||||
}
|
||||
|
||||
constexpr Int128 operator>>(int shift) const {
|
||||
MOZ_ASSERT(0 <= shift && shift <= 127, "undefined shift amount");
|
||||
|
||||
// Ensure the shift amount is in range.
|
||||
shift &= 127;
|
||||
|
||||
// "§2-17 Double-Length Shifts" from Hacker's Delight, 2nd edition.
|
||||
if (shift >= 64) {
|
||||
uint64_t y0 = uint64_t(int64_t(high) >> (shift - 64));
|
||||
uint64_t y1 = uint64_t(int64_t(high) >> 63);
|
||||
return Int128{y0, y1};
|
||||
}
|
||||
uint64_t y0 = low >> shift | high << (64 - shift);
|
||||
uint64_t y1 = uint64_t(int64_t(high) >> shift);
|
||||
return Int128{y0, y1};
|
||||
}
|
||||
|
||||
constexpr Int128 operator&(const Int128& other) const {
|
||||
return Int128{low & other.low, high & other.high};
|
||||
}
|
||||
|
||||
constexpr Int128 operator|(const Int128& other) const {
|
||||
return Int128{low | other.low, high | other.high};
|
||||
}
|
||||
|
||||
constexpr Int128 operator^(const Int128& other) const {
|
||||
return Int128{low ^ other.low, high ^ other.high};
|
||||
}
|
||||
|
||||
constexpr Int128 operator~() const { return Int128{~low, ~high}; }
|
||||
|
||||
constexpr Int128 operator-() const { return Int128{} - *this; }
|
||||
|
||||
constexpr Int128 operator+() const { return *this; }
|
||||
|
||||
constexpr Int128& operator++() {
|
||||
*this = *this + Int128{1, 0};
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator++(int) {
|
||||
auto result = *this;
|
||||
++*this;
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr Int128& operator--() {
|
||||
*this = *this - Int128{1, 0};
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator--(int) {
|
||||
auto result = *this;
|
||||
--*this;
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr Int128 operator+=(const Int128& other) {
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator-=(const Int128& other) {
|
||||
*this = *this - other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator*=(const Int128& other) {
|
||||
*this = *this * other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator/=(const Int128& other) {
|
||||
*this = *this / other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator%=(const Int128& other) {
|
||||
*this = *this % other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator&=(const Int128& other) {
|
||||
*this = *this & other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator|=(const Int128& other) {
|
||||
*this = *this | other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator^=(const Int128& other) {
|
||||
*this = *this ^ other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator<<=(int shift) {
|
||||
*this = *this << shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Int128 operator>>=(int shift) {
|
||||
*this = *this >> shift;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr Uint128::operator Int128() const { return Int128{low, high}; }
|
||||
|
||||
} /* namespace js::temporal */
|
||||
|
||||
template <>
|
||||
class std::numeric_limits<js::temporal::Int128> {
|
||||
public:
|
||||
static constexpr bool is_specialized = true;
|
||||
static constexpr bool is_signed = true;
|
||||
static constexpr bool is_integer = true;
|
||||
static constexpr bool is_exact = true;
|
||||
static constexpr bool has_infinity = false;
|
||||
static constexpr bool has_quiet_NaN = false;
|
||||
static constexpr bool has_signaling_NaN = false;
|
||||
static constexpr std::float_denorm_style has_denorm = std::denorm_absent;
|
||||
static constexpr bool has_denorm_loss = false;
|
||||
static constexpr std::float_round_style round_style = std::round_toward_zero;
|
||||
static constexpr bool is_iec559 = false;
|
||||
static constexpr bool is_bounded = true;
|
||||
static constexpr bool is_modulo = true;
|
||||
static constexpr int digits = CHAR_BIT * sizeof(js::temporal::Int128) - 1;
|
||||
static constexpr int digits10 = int(digits * /* std::log10(2) */ 0.30102999);
|
||||
static constexpr int max_digits10 = 0;
|
||||
static constexpr int radix = 2;
|
||||
static constexpr int min_exponent = 0;
|
||||
static constexpr int min_exponent10 = 0;
|
||||
static constexpr int max_exponent = 0;
|
||||
static constexpr int max_exponent10 = 0;
|
||||
static constexpr bool traps = true;
|
||||
static constexpr bool tinyness_before = false;
|
||||
|
||||
static constexpr auto min() noexcept {
|
||||
return js::temporal::Int128{1} << 127;
|
||||
}
|
||||
static constexpr auto lowest() noexcept { return min(); }
|
||||
static constexpr auto max() noexcept { return ~min(); }
|
||||
static constexpr auto epsilon() noexcept { return js::temporal::Int128{}; }
|
||||
static constexpr auto round_error() noexcept {
|
||||
return js::temporal::Int128{};
|
||||
}
|
||||
static constexpr auto infinity() noexcept { return js::temporal::Int128{}; }
|
||||
static constexpr auto quiet_NaN() noexcept { return js::temporal::Int128{}; }
|
||||
static constexpr auto signaling_NaN() noexcept {
|
||||
return js::temporal::Int128{};
|
||||
}
|
||||
static constexpr auto denorm_min() noexcept { return js::temporal::Int128{}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
class std::numeric_limits<js::temporal::Uint128> {
|
||||
public:
|
||||
static constexpr bool is_specialized = true;
|
||||
static constexpr bool is_signed = false;
|
||||
static constexpr bool is_integer = true;
|
||||
static constexpr bool is_exact = true;
|
||||
static constexpr bool has_infinity = false;
|
||||
static constexpr bool has_quiet_NaN = false;
|
||||
static constexpr bool has_signaling_NaN = false;
|
||||
static constexpr std::float_denorm_style has_denorm = std::denorm_absent;
|
||||
static constexpr bool has_denorm_loss = false;
|
||||
static constexpr std::float_round_style round_style = std::round_toward_zero;
|
||||
static constexpr bool is_iec559 = false;
|
||||
static constexpr bool is_bounded = true;
|
||||
static constexpr bool is_modulo = true;
|
||||
static constexpr int digits = CHAR_BIT * sizeof(js::temporal::Uint128);
|
||||
static constexpr int digits10 = int(digits * /* std::log10(2) */ 0.30102999);
|
||||
static constexpr int max_digits10 = 0;
|
||||
static constexpr int radix = 2;
|
||||
static constexpr int min_exponent = 0;
|
||||
static constexpr int min_exponent10 = 0;
|
||||
static constexpr int max_exponent = 0;
|
||||
static constexpr int max_exponent10 = 0;
|
||||
static constexpr bool traps = true;
|
||||
static constexpr bool tinyness_before = false;
|
||||
|
||||
static constexpr auto min() noexcept { return js::temporal::Uint128{}; }
|
||||
static constexpr auto lowest() noexcept { return min(); }
|
||||
static constexpr auto max() noexcept { return ~js::temporal::Uint128{}; }
|
||||
static constexpr auto epsilon() noexcept { return js::temporal::Uint128{}; }
|
||||
static constexpr auto round_error() noexcept {
|
||||
return js::temporal::Uint128{};
|
||||
}
|
||||
static constexpr auto infinity() noexcept { return js::temporal::Uint128{}; }
|
||||
static constexpr auto quiet_NaN() noexcept { return js::temporal::Uint128{}; }
|
||||
static constexpr auto signaling_NaN() noexcept {
|
||||
return js::temporal::Uint128{};
|
||||
}
|
||||
static constexpr auto denorm_min() noexcept {
|
||||
return js::temporal::Uint128{};
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* builtin_temporal_Int128_h */
|
|
@ -25,6 +25,7 @@
|
|||
#include "NamespaceImports.h"
|
||||
|
||||
#include "builtin/temporal/Instant.h"
|
||||
#include "builtin/temporal/Int128.h"
|
||||
#include "builtin/temporal/PlainDate.h"
|
||||
#include "builtin/temporal/PlainDateTime.h"
|
||||
#include "builtin/temporal/PlainMonthDay.h"
|
||||
|
@ -991,6 +992,36 @@ bool js::temporal::RoundNumberToIncrement(
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// Copied from mozilla::CheckedInt.
|
||||
static bool IsValidMul(const Int128& x, const Int128& y) {
|
||||
static constexpr auto min = Int128{1} << 127;
|
||||
static constexpr auto max = ~min;
|
||||
|
||||
if (x == Int128{0} || y == Int128{0}) {
|
||||
return true;
|
||||
}
|
||||
if (x > Int128{0}) {
|
||||
return y > Int128{0} ? x <= max / y : y >= min / x;
|
||||
}
|
||||
return y > Int128{0} ? x >= min / y : y >= max / x;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
Int128 js::temporal::RoundNumberToIncrement(const Int128& numerator,
|
||||
const Int128& increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
// Steps 1-8.
|
||||
auto rounded = Divide(numerator, increment, roundingMode);
|
||||
|
||||
// Step 9.
|
||||
MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow");
|
||||
return rounded * increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* ToCalendarNameOption ( normalizedOptions )
|
||||
*/
|
||||
|
|
|
@ -37,6 +37,7 @@ class TemporalObject : public NativeObject {
|
|||
|
||||
struct Instant;
|
||||
struct PlainTime;
|
||||
class Int128;
|
||||
|
||||
/**
|
||||
* Rounding increment, which is an integer in the range [1, 1'000'000'000].
|
||||
|
@ -168,6 +169,12 @@ bool GetTemporalUnit(JSContext* cx, JS::Handle<JSString*> value,
|
|||
bool ToTemporalRoundingMode(JSContext* cx, JS::Handle<JSObject*> options,
|
||||
TemporalRoundingMode* mode);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
Int128 RoundNumberToIncrement(const Int128& x, const Int128& increment,
|
||||
TemporalRoundingMode roundingMode);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <cmath>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/temporal/Int128.h"
|
||||
|
||||
namespace js::temporal {
|
||||
|
||||
// Overview of integer rounding modes is available at
|
||||
|
@ -428,6 +430,286 @@ inline int64_t Divide(int64_t dividend, int64_t divisor,
|
|||
MOZ_CRASH("invalid rounding mode");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute ceiling division ⌈dividend / divisor⌉. The divisor must be a positive
|
||||
* number.
|
||||
*/
|
||||
constexpr Int128 CeilDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// Ceiling division rounds the quotient toward positive infinity. When the
|
||||
// quotient is negative, this is equivalent to rounding toward zero. See [1].
|
||||
//
|
||||
// Int128 division truncates, so rounding toward zero for negative quotients
|
||||
// is already covered. When there is a non-zero positive remainder, the
|
||||
// quotient is positive and we have to increment it by one to implement
|
||||
// rounding toward positive infinity.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (remainder > Int128{0}) {
|
||||
quotient += Int128{1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute floor division ⌊dividend / divisor⌋. The divisor must be a positive
|
||||
* number.
|
||||
*/
|
||||
constexpr Int128 FloorDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// Floor division rounds the quotient toward negative infinity. When the
|
||||
// quotient is positive, this is equivalent to rounding toward zero. See [1].
|
||||
//
|
||||
// Int128 division truncates, so rounding toward zero for positive quotients
|
||||
// is already covered. When there is a non-zero negative remainder, the
|
||||
// quotient is negative and we have to decrement it by one to implement
|
||||
// rounding toward negative infinity.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (remainder < Int128{0}) {
|
||||
quotient -= Int128{1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute "round toward infinity" division `dividend / divisor`. The divisor
|
||||
* must be a positive number.
|
||||
*/
|
||||
constexpr Int128 ExpandDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// "Round toward infinity" division rounds positive quotients toward positive
|
||||
// infinity and negative quotients toward negative infinity. See [1].
|
||||
//
|
||||
// When there is a non-zero positive remainder, the quotient is positive and
|
||||
// we have to increment it by one to implement rounding toward positive
|
||||
// infinity. When there is a non-zero negative remainder, the quotient is
|
||||
// negative and we have to decrement it by one to implement rounding toward
|
||||
// negative infinity.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (remainder > Int128{0}) {
|
||||
quotient += Int128{1};
|
||||
}
|
||||
if (remainder < Int128{0}) {
|
||||
quotient -= Int128{1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute truncating division `dividend / divisor`. The divisor must be a
|
||||
* positive number.
|
||||
*/
|
||||
constexpr Int128 TruncDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
// Truncating division rounds both positive and negative quotients toward
|
||||
// zero, cf. [1].
|
||||
//
|
||||
// Int128 division truncates, so rounding toward zero implicitly happens.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
return dividend / divisor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute "round half toward positive infinity" division `dividend / divisor`.
|
||||
* The divisor must be a positive number.
|
||||
*/
|
||||
inline Int128 HalfCeilDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// "Round half toward positive infinity" division rounds the quotient toward
|
||||
// positive infinity when the fractional part of the remainder is ≥0.5. When
|
||||
// the quotient is negative, this is equivalent to rounding toward zero
|
||||
// instead of toward positive infinity. See [1].
|
||||
//
|
||||
// When the remainder is a non-zero positive value, the quotient is positive,
|
||||
// too. When additionally the fractional part of the remainder is ≥0.5, we
|
||||
// have to increment the quotient by one to implement rounding toward positive
|
||||
// infinity.
|
||||
//
|
||||
// Int128 division truncates, so we implicitly round toward zero for negative
|
||||
// quotients. When the absolute value of the fractional part of the remainder
|
||||
// is >0.5, we should instead have rounded toward negative infinity, so we
|
||||
// need to decrement the quotient by one.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (remainder > Int128{0} &&
|
||||
Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) {
|
||||
quotient += Int128{1};
|
||||
}
|
||||
if (remainder < Int128{0} &&
|
||||
Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
|
||||
quotient -= Int128{1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute "round half toward negative infinity" division `dividend / divisor`.
|
||||
* The divisor must be a positive number.
|
||||
*/
|
||||
inline Int128 HalfFloorDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// "Round half toward negative infinity" division rounds the quotient toward
|
||||
// negative infinity when the fractional part of the remainder is ≥0.5. When
|
||||
// the quotient is positive, this is equivalent to rounding toward zero
|
||||
// instead of toward negative infinity. See [1].
|
||||
//
|
||||
// When the remainder is a non-zero negative value, the quotient is negative,
|
||||
// too. When additionally the fractional part of the remainder is ≥0.5, we
|
||||
// have to decrement the quotient by one to implement rounding toward negative
|
||||
// infinity.
|
||||
//
|
||||
// Int128 division truncates, so we implicitly round toward zero for positive
|
||||
// quotients. When the absolute value of the fractional part of the remainder
|
||||
// is >0.5, we should instead have rounded toward positive infinity, so we
|
||||
// need to increment the quotient by one.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (remainder < Int128{0} &&
|
||||
Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) {
|
||||
quotient -= Int128{1};
|
||||
}
|
||||
if (remainder > Int128{0} &&
|
||||
Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
|
||||
quotient += Int128{1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute "round half toward infinity" division `dividend / divisor`. The
|
||||
* divisor must be a positive number.
|
||||
*/
|
||||
inline Int128 HalfExpandDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// "Round half toward infinity" division rounds positive quotients whose
|
||||
// remainder has a fractional part ≥0.5 toward positive infinity. And negative
|
||||
// quotients whose remainder has a fractional part ≥0.5 toward negative
|
||||
// infinity. See [1].
|
||||
//
|
||||
// Int128 division truncates, which means it rounds toward zero, so we have
|
||||
// to increment resp. decrement the quotient when the fractional part of the
|
||||
// remainder is ≥0.5 to round toward ±infinity.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) {
|
||||
quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute "round half toward zero" division `dividend / divisor`. The divisor
|
||||
* must be a positive number.
|
||||
*/
|
||||
inline Int128 HalfTruncDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// "Round half toward zero" division rounds both positive and negative
|
||||
// quotients whose remainder has a fractional part ≤0.5 toward zero. See [1].
|
||||
//
|
||||
// Int128 division truncates, so we implicitly round toward zero. When the
|
||||
// fractional part of the remainder is >0.5, we should instead have rounded
|
||||
// toward ±infinity, so we need to increment resp. decrement the quotient by
|
||||
// one.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if (Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
|
||||
quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute "round half to even" division `dividend / divisor`. The divisor must
|
||||
* be a positive number.
|
||||
*/
|
||||
inline Int128 HalfEvenDiv(const Int128& dividend, const Int128& divisor) {
|
||||
MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported");
|
||||
|
||||
auto [quotient, remainder] = dividend.divrem(divisor);
|
||||
|
||||
// "Round half to even" division rounds both positive and negative quotients
|
||||
// to the nearest even integer. See [1].
|
||||
//
|
||||
// Int128 division truncates, so we implicitly round toward zero. When the
|
||||
// fractional part of the remainder is 0.5 and the quotient is odd or when the
|
||||
// fractional part of the remainder is >0.5, we should instead have rounded
|
||||
// toward ±infinity, so we need to increment resp. decrement the quotient by
|
||||
// one.
|
||||
//
|
||||
// [1]
|
||||
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
|
||||
if ((quotient & Int128{1}) == Int128{1} &&
|
||||
Uint128(remainder.abs()) * Uint128{2} == static_cast<Uint128>(divisor)) {
|
||||
quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
|
||||
}
|
||||
if (Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) {
|
||||
quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1};
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform `dividend / divisor` and round the result according to the given
|
||||
* rounding mode.
|
||||
*/
|
||||
inline Int128 Divide(const Int128& dividend, const Int128& divisor,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
switch (roundingMode) {
|
||||
case TemporalRoundingMode::Ceil:
|
||||
return CeilDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::Floor:
|
||||
return FloorDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::Expand:
|
||||
return ExpandDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::Trunc:
|
||||
return TruncDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::HalfCeil:
|
||||
return HalfCeilDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::HalfFloor:
|
||||
return HalfFloorDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::HalfExpand:
|
||||
return HalfExpandDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::HalfTrunc:
|
||||
return HalfTruncDiv(dividend, divisor);
|
||||
case TemporalRoundingMode::HalfEven:
|
||||
return HalfEvenDiv(dividend, divisor);
|
||||
}
|
||||
MOZ_CRASH("invalid rounding mode");
|
||||
}
|
||||
|
||||
} /* namespace js::temporal */
|
||||
|
||||
#endif /* builtin_temporal_TemporalRoundingMode_h */
|
||||
|
|
|
@ -17,6 +17,7 @@ if CONFIG["JS_HAS_TEMPORAL_API"]:
|
|||
"Calendar.cpp",
|
||||
"Duration.cpp",
|
||||
"Instant.cpp",
|
||||
"Int128.cpp",
|
||||
"Int96.cpp",
|
||||
"PlainDate.cpp",
|
||||
"PlainDateTime.cpp",
|
||||
|
|
|
@ -74,6 +74,7 @@ UNIFIED_SOURCES += [
|
|||
"testHashTable.cpp",
|
||||
"testIndexToString.cpp",
|
||||
"testInformalValueTypeName.cpp",
|
||||
"testInt128.cpp",
|
||||
"testIntern.cpp",
|
||||
"testIntlAvailableLocales.cpp",
|
||||
"testIntString.cpp",
|
||||
|
|
|
@ -0,0 +1,576 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifdef JS_HAS_TEMPORAL_API
|
||||
|
||||
# include "mozilla/TextUtils.h"
|
||||
|
||||
# include <array>
|
||||
# include <climits>
|
||||
# include <limits>
|
||||
# include <optional>
|
||||
# include <stdint.h>
|
||||
# include <utility>
|
||||
|
||||
# include "builtin/temporal/Int128.h"
|
||||
# include "jsapi-tests/tests.h"
|
||||
|
||||
using Int128 = js::temporal::Int128;
|
||||
using Uint128 = js::temporal::Uint128;
|
||||
|
||||
// Simple Uint128 parser.
|
||||
template <char... DIGITS>
|
||||
constexpr Uint128 operator""_u128() {
|
||||
static_assert(sizeof...(DIGITS) > 0);
|
||||
|
||||
constexpr auto digits = std::array{DIGITS...};
|
||||
|
||||
constexpr auto isBinaryDigit = [](auto c) {
|
||||
return (c >= '0' && c <= '1') || c == '\'';
|
||||
};
|
||||
|
||||
constexpr auto isOctalDigit = [](auto c) {
|
||||
return (c >= '0' && c <= '7') || c == '\'';
|
||||
};
|
||||
|
||||
constexpr auto isDigit = [](auto c) {
|
||||
return mozilla::IsAsciiDigit(c) || c == '\'';
|
||||
};
|
||||
|
||||
constexpr auto isHexDigit = [](auto c) {
|
||||
return mozilla::IsAsciiHexDigit(c) || c == '\'';
|
||||
};
|
||||
|
||||
constexpr auto isBinary = [isBinaryDigit](auto zero, auto prefix,
|
||||
auto... rest) {
|
||||
return zero == '0' && (prefix == 'b' || prefix == 'B') &&
|
||||
(isBinaryDigit(rest) && ...);
|
||||
};
|
||||
|
||||
constexpr auto isHex = [isHexDigit](auto zero, auto prefix, auto... rest) {
|
||||
return zero == '0' && (prefix == 'x' || prefix == 'X') &&
|
||||
(isHexDigit(rest) && ...);
|
||||
};
|
||||
|
||||
constexpr auto binary = [digits]() -> std::optional<Uint128> {
|
||||
auto value = Uint128{};
|
||||
for (size_t i = 2; i < digits.size(); ++i) {
|
||||
auto digit = digits[i];
|
||||
if (digit == '\'') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect overflow.
|
||||
if (((value << 1) >> 1) != value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value = (value << 1) | Uint128{uint64_t(digit - '0')};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
constexpr auto octal = [digits]() -> std::optional<Uint128> {
|
||||
auto value = Uint128{};
|
||||
for (size_t i = 1; i < digits.size(); ++i) {
|
||||
auto digit = digits[i];
|
||||
if (digit == '\'') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect overflow.
|
||||
if (((value << 3) >> 3) != value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value = (value << 3) | Uint128{uint64_t(digit - '0')};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
constexpr auto decimal = [digits]() -> std::optional<Uint128> {
|
||||
auto value = Uint128{};
|
||||
for (size_t i = 0; i < digits.size(); ++i) {
|
||||
auto digit = digits[i];
|
||||
if (digit == '\'') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NB: Overflow check not implemented.
|
||||
value = (value * Uint128{10}) + Uint128{uint64_t(digit - '0')};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
constexpr auto hexadecimal = [digits]() -> std::optional<Uint128> {
|
||||
auto value = Uint128{};
|
||||
for (size_t i = 2; i < digits.size(); ++i) {
|
||||
auto digit = digits[i];
|
||||
if (digit == '\'') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect overflow.
|
||||
if (((value << 4) >> 4) != value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
value =
|
||||
(value << 4) | Uint128{uint64_t(digit >= 'a' ? (digit - 'a') + 10
|
||||
: digit >= 'A' ? (digit - 'A') + 10
|
||||
: digit - '0')};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
if constexpr (digits.size() > 2 && digits[0] == '0' &&
|
||||
!mozilla::IsAsciiDigit(digits[1])) {
|
||||
if constexpr (isBinary(DIGITS...)) {
|
||||
if constexpr (constexpr auto value = binary()) {
|
||||
return *value;
|
||||
} else {
|
||||
static_assert(false, "binary literal too large");
|
||||
}
|
||||
} else if constexpr (isHex(DIGITS...)) {
|
||||
if constexpr (constexpr auto value = hexadecimal()) {
|
||||
return *value;
|
||||
} else {
|
||||
static_assert(false, "hexadecimal literal too large");
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "invalid prefix literal");
|
||||
}
|
||||
} else if constexpr (digits.size() > 1 && digits[0] == '0') {
|
||||
if constexpr ((isOctalDigit(DIGITS) && ...)) {
|
||||
if constexpr (constexpr auto value = octal()) {
|
||||
return *value;
|
||||
} else {
|
||||
static_assert(false, "octal literal too large");
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "invalid octal literal");
|
||||
}
|
||||
} else if constexpr ((isDigit(DIGITS) && ...)) {
|
||||
if constexpr (constexpr auto value = decimal()) {
|
||||
return *value;
|
||||
} else {
|
||||
static_assert(false, "decimal literal too large");
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "invalid literal");
|
||||
}
|
||||
}
|
||||
|
||||
template <char... DIGITS>
|
||||
constexpr Int128 operator""_i128() {
|
||||
return Int128{operator""_u128 < DIGITS... > ()};
|
||||
}
|
||||
|
||||
template <typename T, size_t N, size_t... ISeq>
|
||||
static constexpr auto to_array_impl(const T (&elements)[N],
|
||||
std::index_sequence<ISeq...>) {
|
||||
return std::array<T, N>{{elements[ISeq]...}};
|
||||
}
|
||||
|
||||
// No std::to_array because we don't yet compile with C++20.
|
||||
template <typename T, size_t N>
|
||||
static constexpr auto to_array(const T (&elements)[N]) {
|
||||
return to_array_impl(elements, std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
class ConversionFixture : public JSAPIRuntimeTest {
|
||||
public:
|
||||
virtual ~ConversionFixture() = default;
|
||||
|
||||
template <typename T, typename U, size_t N>
|
||||
bool testConversion(const std::array<U, N>& values);
|
||||
};
|
||||
|
||||
template <typename T, typename U, size_t N>
|
||||
bool ConversionFixture::testConversion(const std::array<U, N>& values) {
|
||||
for (auto v : values) {
|
||||
// Conversion to signed int.
|
||||
CHECK_EQUAL(int64_t(T{v}), int64_t(v));
|
||||
CHECK_EQUAL(int32_t(T{v}), int32_t(v));
|
||||
CHECK_EQUAL(int16_t(T{v}), int16_t(v));
|
||||
CHECK_EQUAL(int8_t(T{v}), int8_t(v));
|
||||
|
||||
// Conversion to unsigned int.
|
||||
CHECK_EQUAL(uint64_t(T{v}), uint64_t(v));
|
||||
CHECK_EQUAL(uint32_t(T{v}), uint32_t(v));
|
||||
CHECK_EQUAL(uint16_t(T{v}), uint16_t(v));
|
||||
CHECK_EQUAL(uint8_t(T{v}), uint8_t(v));
|
||||
|
||||
// Conversion to double.
|
||||
CHECK_EQUAL(double(T{v}), double(v));
|
||||
|
||||
// Conversion to bool.
|
||||
CHECK_EQUAL(bool(T{v}), bool(v));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BEGIN_FIXTURE_TEST(ConversionFixture, testInt128_conversion) {
|
||||
auto values = to_array<int64_t>({
|
||||
INT64_MIN,
|
||||
INT64_MIN + 1,
|
||||
int64_t(INT32_MIN) - 1,
|
||||
INT32_MIN,
|
||||
INT32_MIN + 1,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
INT32_MAX - 1,
|
||||
INT32_MAX,
|
||||
int64_t(INT32_MAX) + 1,
|
||||
INT64_MAX - 1,
|
||||
INT64_MAX,
|
||||
});
|
||||
|
||||
CHECK(testConversion<Int128>(values));
|
||||
|
||||
return true;
|
||||
}
|
||||
END_FIXTURE_TEST(ConversionFixture, testInt128_conversion)
|
||||
|
||||
BEGIN_FIXTURE_TEST(ConversionFixture, testUint128_conversion) {
|
||||
auto values = to_array<uint64_t>({
|
||||
0,
|
||||
1,
|
||||
UINT32_MAX - 1,
|
||||
UINT32_MAX,
|
||||
uint64_t(UINT32_MAX) + 1,
|
||||
UINT64_MAX - 1,
|
||||
UINT64_MAX,
|
||||
});
|
||||
|
||||
CHECK(testConversion<Uint128>(values));
|
||||
|
||||
return true;
|
||||
}
|
||||
END_FIXTURE_TEST(ConversionFixture, testUint128_conversion)
|
||||
|
||||
class OperatorFixture : public JSAPIRuntimeTest {
|
||||
public:
|
||||
virtual ~OperatorFixture() = default;
|
||||
|
||||
template <typename T, typename U, size_t N>
|
||||
bool testOperator(const std::array<U, N>& values);
|
||||
};
|
||||
|
||||
template <typename T, typename U, size_t N>
|
||||
bool OperatorFixture::testOperator(const std::array<U, N>& values) {
|
||||
// Unary operators.
|
||||
for (auto x : values) {
|
||||
// Sign operators.
|
||||
CHECK_EQUAL(U(+T{x}), +x);
|
||||
CHECK_EQUAL(U(-T{x}), -x);
|
||||
|
||||
// Bitwise operators.
|
||||
CHECK_EQUAL(U(~T{x}), ~x);
|
||||
|
||||
// Increment/Decrement operators.
|
||||
auto y = T{x};
|
||||
CHECK_EQUAL(U(++y), x + 1);
|
||||
CHECK_EQUAL(U(y), x + 1);
|
||||
|
||||
y = T{x};
|
||||
CHECK_EQUAL(U(y++), x);
|
||||
CHECK_EQUAL(U(y), x + 1);
|
||||
|
||||
y = T{x};
|
||||
CHECK_EQUAL(U(--y), x - 1);
|
||||
CHECK_EQUAL(U(y), x - 1);
|
||||
|
||||
y = T{x};
|
||||
CHECK_EQUAL(U(y--), x);
|
||||
CHECK_EQUAL(U(y), x - 1);
|
||||
}
|
||||
|
||||
// Binary operators.
|
||||
for (auto x : values) {
|
||||
for (auto y : values) {
|
||||
// Comparison operators.
|
||||
CHECK_EQUAL((T{x} == T{y}), (x == y));
|
||||
CHECK_EQUAL((T{x} != T{y}), (x != y));
|
||||
CHECK_EQUAL((T{x} < T{y}), (x < y));
|
||||
CHECK_EQUAL((T{x} <= T{y}), (x <= y));
|
||||
CHECK_EQUAL((T{x} > T{y}), (x > y));
|
||||
CHECK_EQUAL((T{x} >= T{y}), (x >= y));
|
||||
|
||||
// Add/Sub/Mul operators.
|
||||
CHECK_EQUAL(U(T{x} + T{y}), (x + y));
|
||||
CHECK_EQUAL(U(T{x} - T{y}), (x - y));
|
||||
CHECK_EQUAL(U(T{x} * T{y}), (x * y));
|
||||
|
||||
// Division operators.
|
||||
if (y != 0) {
|
||||
CHECK_EQUAL(U(T{x} / T{y}), (x / y));
|
||||
CHECK_EQUAL(U(T{x} % T{y}), (x % y));
|
||||
}
|
||||
|
||||
// Shift operators.
|
||||
if (y >= 0) {
|
||||
CHECK_EQUAL(U(T{x} << y), (x << y));
|
||||
CHECK_EQUAL(U(T{x} >> y), (x >> y));
|
||||
}
|
||||
|
||||
// Bitwise operators.
|
||||
CHECK_EQUAL(U(T{x} & T{y}), (x & y));
|
||||
CHECK_EQUAL(U(T{x} | T{y}), (x | y));
|
||||
CHECK_EQUAL(U(T{x} ^ T{y}), (x ^ y));
|
||||
}
|
||||
}
|
||||
|
||||
// Compound assignment operators.
|
||||
for (auto x : values) {
|
||||
for (auto y : values) {
|
||||
auto z = T{x};
|
||||
z += T{y};
|
||||
CHECK_EQUAL(U(z), x + y);
|
||||
|
||||
z = T{x};
|
||||
z -= T{y};
|
||||
CHECK_EQUAL(U(z), x - y);
|
||||
|
||||
z = T{x};
|
||||
z *= T{y};
|
||||
CHECK_EQUAL(U(z), x * y);
|
||||
|
||||
if (y != 0) {
|
||||
z = T{x};
|
||||
z /= T{y};
|
||||
CHECK_EQUAL(U(z), x / y);
|
||||
|
||||
z = T{x};
|
||||
z %= T{y};
|
||||
CHECK_EQUAL(U(z), x % y);
|
||||
}
|
||||
|
||||
if (y >= 0) {
|
||||
z = T{x};
|
||||
z <<= y;
|
||||
CHECK_EQUAL(U(z), x << y);
|
||||
|
||||
z = T{x};
|
||||
z >>= y;
|
||||
CHECK_EQUAL(U(z), x >> y);
|
||||
}
|
||||
|
||||
z = T{x};
|
||||
z &= T{y};
|
||||
CHECK_EQUAL(U(z), x & y);
|
||||
|
||||
z = T{x};
|
||||
z |= T{y};
|
||||
CHECK_EQUAL(U(z), x | y);
|
||||
|
||||
z = T{x};
|
||||
z ^= T{y};
|
||||
CHECK_EQUAL(U(z), x ^ y);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BEGIN_FIXTURE_TEST(OperatorFixture, testInt128_operator) {
|
||||
auto values = to_array<int64_t>({
|
||||
-3,
|
||||
-2,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
63,
|
||||
});
|
||||
|
||||
CHECK(testOperator<Int128>(values));
|
||||
|
||||
// Values larger than INT64_MAX.
|
||||
CHECK((Int128{INT64_MAX} * Int128{2}) ==
|
||||
(Int128{INT64_MAX} + Int128{INT64_MAX}));
|
||||
CHECK((Int128{INT64_MAX} * Int128{3}) ==
|
||||
(Int128{INT64_MAX} * Int128{4} - Int128{INT64_MAX}));
|
||||
CHECK((Int128{INT64_MAX} * Int128{2}) == (Int128{INT64_MAX} << 1));
|
||||
CHECK((Int128{INT64_MAX} * Int128{8}) == (Int128{INT64_MAX} << 3));
|
||||
CHECK((Int128{INT64_MAX} * Int128{8} / Int128{2}) ==
|
||||
(Int128{INT64_MAX} << 2));
|
||||
CHECK((Int128{INT64_MAX} * Int128{23} % Int128{13}) == (Int128{5}));
|
||||
|
||||
// Values smaller than INT64_MIN.
|
||||
CHECK((Int128{INT64_MIN} * Int128{2}) ==
|
||||
(Int128{INT64_MIN} + Int128{INT64_MIN}));
|
||||
CHECK((Int128{INT64_MIN} * Int128{3}) ==
|
||||
(Int128{INT64_MIN} * Int128{4} - Int128{INT64_MIN}));
|
||||
CHECK((Int128{INT64_MIN} * Int128{2}) == (Int128{INT64_MIN} << 1));
|
||||
CHECK((Int128{INT64_MIN} * Int128{8}) == (Int128{INT64_MIN} << 3));
|
||||
CHECK((Int128{INT64_MIN} * Int128{8} / Int128{2}) ==
|
||||
(Int128{INT64_MIN} << 2));
|
||||
CHECK((Int128{INT64_MIN} * Int128{23} % Int128{13}) == (Int128{-2}));
|
||||
|
||||
return true;
|
||||
}
|
||||
END_FIXTURE_TEST(OperatorFixture, testInt128_operator)
|
||||
|
||||
BEGIN_FIXTURE_TEST(OperatorFixture, testUint128_operator) {
|
||||
auto values = to_array<uint64_t>({
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
63,
|
||||
});
|
||||
|
||||
CHECK(testOperator<Uint128>(values));
|
||||
|
||||
// Values larger than UINT64_MAX.
|
||||
CHECK((Uint128{UINT64_MAX} * Uint128{2}) ==
|
||||
(Uint128{UINT64_MAX} + Uint128{UINT64_MAX}));
|
||||
CHECK((Uint128{UINT64_MAX} * Uint128{3}) ==
|
||||
(Uint128{UINT64_MAX} * Uint128{4} - Uint128{UINT64_MAX}));
|
||||
CHECK((Uint128{UINT64_MAX} * Uint128{2}) == (Uint128{UINT64_MAX} << 1));
|
||||
CHECK((Uint128{UINT64_MAX} * Uint128{8}) == (Uint128{UINT64_MAX} << 3));
|
||||
CHECK((Uint128{UINT64_MAX} * Uint128{8} / Uint128{2}) ==
|
||||
(Uint128{UINT64_MAX} << 2));
|
||||
CHECK((Uint128{UINT64_MAX} * Uint128{23} % Uint128{13}) == (Uint128{7}));
|
||||
|
||||
return true;
|
||||
}
|
||||
END_FIXTURE_TEST(OperatorFixture, testUint128_operator)
|
||||
|
||||
BEGIN_TEST(testInt128_literal) {
|
||||
CHECK_EQUAL(int64_t(0x7fff'ffff'ffff'ffff_i128), INT64_MAX);
|
||||
CHECK_EQUAL(int64_t(-0x8000'0000'0000'0000_i128), INT64_MIN);
|
||||
|
||||
CHECK(std::numeric_limits<Int128>::max() ==
|
||||
0x7fff'ffff'ffff'ffff'ffff'ffff'ffff'ffff_i128);
|
||||
CHECK(std::numeric_limits<Int128>::min() ==
|
||||
-0x8000'0000'0000'0000'0000'0000'0000'0000_i128);
|
||||
|
||||
auto x = (Int128{INT64_MAX} + Int128{1}) * Int128{3};
|
||||
CHECK(x == 27670116110564327424_i128);
|
||||
CHECK(x == 0x1'8000'0000'0000'0000_i128);
|
||||
|
||||
auto y = Int128{0} - (Int128{5} * Int128{INT64_MAX});
|
||||
CHECK(y == -46116860184273879035_i128);
|
||||
CHECK(y == -0x2'7fff'ffff'ffff'fffb_i128);
|
||||
|
||||
// NB: This shift expression overflows.
|
||||
auto z = Int128{0x1122'3344} << 100;
|
||||
CHECK(z == 0x1223'3440'0000'0000'0000'0000'0000'0000_i128);
|
||||
CHECK(z == 0221063210000000000000000000000000000000000_i128);
|
||||
CHECK(z == 24108894070078995479046745700448600064_i128);
|
||||
CHECK(
|
||||
z ==
|
||||
0b10010001000110011010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_i128);
|
||||
|
||||
z >>= 80;
|
||||
CHECK(z == 0X1223'3440'0000_i128);
|
||||
CHECK(z == 0442146420000000_i128);
|
||||
CHECK(z == 19942409764864_i128);
|
||||
CHECK(z == 0B100100010001100110100010000000000000000000000_i128);
|
||||
|
||||
auto v = Int128{INT64_MAX} * Int128{INT64_MAX};
|
||||
CHECK(v == 0x3fff'ffff'ffff'ffff'0000'0000'0000'0001_i128);
|
||||
CHECK((v + v) == 0x7fff'ffff'ffff'fffe'0000'0000'0000'0002_i128);
|
||||
CHECK((v * v) == 0x7fff'ffff'ffff'fffe'0000'0000'0000'0001_i128);
|
||||
CHECK((v * -v) == -0x7fff'ffff'ffff'fffe'0000'0000'0000'0001_i128);
|
||||
CHECK((-v * v) == -0x7fff'ffff'ffff'fffe'0000'0000'0000'0001_i128);
|
||||
CHECK((-v * -v) == 0x7fff'ffff'ffff'fffe'0000'0000'0000'0001_i128);
|
||||
|
||||
auto w = Int128{INT64_MIN} * Int128{INT64_MIN};
|
||||
CHECK(w == 0x4000'0000'0000'0000'0000'0000'0000'0000_i128);
|
||||
CHECK((w + w) == -0x8000'0000'0000'0000'0000'0000'0000'0000_i128);
|
||||
CHECK((w * w) == 0_i128);
|
||||
|
||||
CHECK((Int128{1} << 120) == 0x100'0000'0000'0000'0000'0000'0000'0000_i128);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testInt128_literal)
|
||||
|
||||
BEGIN_TEST(testUint128_literal) {
|
||||
CHECK_EQUAL(uint64_t(0xffff'ffff'ffff'ffff_u128), UINT64_MAX);
|
||||
|
||||
CHECK(std::numeric_limits<Uint128>::max() ==
|
||||
0xffff'ffff'ffff'ffff'ffff'ffff'ffff'ffff_u128);
|
||||
|
||||
auto x = (Uint128{UINT64_MAX} + Uint128{3}) * Uint128{3};
|
||||
CHECK(x == 55340232221128654854_u128);
|
||||
CHECK(x == 0x3'0000'0000'0000'0006_u128);
|
||||
|
||||
// NB: This shift expression overflows.
|
||||
auto z = Uint128{0x1122'3344} << 100;
|
||||
CHECK(z == 0x1223'3440'0000'0000'0000'0000'0000'0000_u128);
|
||||
CHECK(z == 0221063210000000000000000000000000000000000_u128);
|
||||
CHECK(z == 24108894070078995479046745700448600064_u128);
|
||||
CHECK(
|
||||
z ==
|
||||
0b10010001000110011010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128);
|
||||
|
||||
z >>= 80;
|
||||
CHECK(z == 0X1223'3440'0000_u128);
|
||||
CHECK(z == 0442146420000000_u128);
|
||||
CHECK(z == 19942409764864_u128);
|
||||
CHECK(z == 0B100100010001100110100010000000000000000000000_u128);
|
||||
|
||||
auto v = Uint128{UINT64_MAX} * Uint128{UINT64_MAX};
|
||||
CHECK(v == 0xffff'ffff'ffff'fffe'0000'0000'0000'0001_u128);
|
||||
CHECK((v + v) == 0xffff'ffff'ffff'fffc'0000'0000'0000'0002_u128);
|
||||
CHECK((v * v) == 0xffff'ffff'ffff'fffc'0000'0000'0000'0001_u128);
|
||||
CHECK((v * -v) == 0x3'ffff'ffff'ffff'ffff_u128);
|
||||
CHECK((-v * v) == 0x3'ffff'ffff'ffff'ffff_u128);
|
||||
CHECK((-v * -v) == 0xffff'ffff'ffff'fffc'0000'0000'0000'0001_u128);
|
||||
|
||||
CHECK((Uint128{1} << 120) == 0x100'0000'0000'0000'0000'0000'0000'0000_u128);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testUint128_literal)
|
||||
|
||||
BEGIN_TEST(testInt128_division) {
|
||||
auto x = Int128{INT64_MAX} * Int128{4};
|
||||
CHECK((x / Int128{2}) == 0xffff'ffff'ffff'fffe_i128);
|
||||
CHECK((x / Int128{2}) == (x >> 1));
|
||||
|
||||
auto y = Int128{INT64_MAX} * Int128{16};
|
||||
CHECK((y / Int128{2}) == 0x3'ffff'ffff'ffff'fff8_i128);
|
||||
CHECK((y / Int128{2}) == (y >> 1));
|
||||
|
||||
CHECK((0x1122'3344'5566'7788'aabb'ccdd'ff12'3456_i128 / 7_i128) ==
|
||||
0x272'999c'0c33'35a5'cf3f'6668'db4b'be55_i128);
|
||||
CHECK((0x1122'3344'5566'7788'aabb'ccdd'ff12'3456_i128 /
|
||||
0x1'2345'6789'abcd'ef11'abcd'ef11_i128) == 0xf0f0f0f_i128);
|
||||
CHECK((7_i128 / 0x1122'3344'5566'7788'aabb'ccdd'ff12'3456_i128) == 0_i128);
|
||||
|
||||
CHECK((0x1122'3344'5566'7788'aabb'ccdd'ff12'3456_i128 % 7_i128) == 3_i128);
|
||||
CHECK((0x1122'3344'5566'7788'aabb'ccdd'ff12'3456_i128 %
|
||||
0x1'2345'6789'abcd'ef11'abcd'ef11_i128) ==
|
||||
0x1122'3353'7d8e'9fb0'dc00'3357_i128);
|
||||
CHECK((7_i128 % 0x1122'3344'5566'7788'aabb'ccdd'ff12'3456_i128) == 7_i128);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testInt128_division)
|
||||
|
||||
BEGIN_TEST(testInt128_abs) {
|
||||
CHECK((0_i128).abs() == 0_u128);
|
||||
|
||||
CHECK((0x1122'3344_i128).abs() == 0x1122'3344_u128);
|
||||
CHECK((-0x1122'3344_i128).abs() == 0x1122'3344_u128);
|
||||
|
||||
CHECK((0x1111'2222'3333'4444'5555'6666'7777'8888_i128).abs() ==
|
||||
0x1111'2222'3333'4444'5555'6666'7777'8888_u128);
|
||||
CHECK((-0x1111'2222'3333'4444'5555'6666'7777'8888_i128).abs() ==
|
||||
0x1111'2222'3333'4444'5555'6666'7777'8888_u128);
|
||||
|
||||
CHECK(std::numeric_limits<Int128>::min().abs() ==
|
||||
0x8000'0000'0000'0000'0000'0000'0000'0000_u128);
|
||||
CHECK(std::numeric_limits<Int128>::max().abs() ==
|
||||
0x7fff'ffff'ffff'ffff'ffff'ffff'ffff'ffff_u128);
|
||||
|
||||
return true;
|
||||
}
|
||||
END_TEST(testInt128_abs)
|
||||
|
||||
#endif
|
Загрузка…
Ссылка в новой задаче