diff options
Diffstat (limited to 'js/src/builtin/temporal/TemporalRoundingMode.h')
-rw-r--r-- | js/src/builtin/temporal/TemporalRoundingMode.h | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/TemporalRoundingMode.h b/js/src/builtin/temporal/TemporalRoundingMode.h index 91ef758fc6..23d3996d09 100644 --- a/js/src/builtin/temporal/TemporalRoundingMode.h +++ b/js/src/builtin/temporal/TemporalRoundingMode.h @@ -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 */ |