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:
André Bargull 2024-04-15 18:27:17 +00:00
Родитель 880e45e3ae
Коммит 6920618be1
8 изменённых файлов: 1801 добавлений и 0 удалений

Просмотреть файл

@ -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