From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/builtin/temporal/TemporalRoundingMode.h | 433 +++++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 js/src/builtin/temporal/TemporalRoundingMode.h (limited to 'js/src/builtin/temporal/TemporalRoundingMode.h') diff --git a/js/src/builtin/temporal/TemporalRoundingMode.h b/js/src/builtin/temporal/TemporalRoundingMode.h new file mode 100644 index 0000000000..91ef758fc6 --- /dev/null +++ b/js/src/builtin/temporal/TemporalRoundingMode.h @@ -0,0 +1,433 @@ +/* -*- 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_TemporalRoundingMode_h +#define builtin_temporal_TemporalRoundingMode_h + +#include "mozilla/Assertions.h" + +#include +#include + +namespace js::temporal { + +// Overview of integer rounding modes is available at +// . +enum class TemporalRoundingMode { + // 1. Directed rounding to an integer. + + // Round toward positive infinity. + Ceil, + + // Round toward negative infinity. + Floor, + + // Round toward infinity or round away from zero. + Expand, + + // Round toward zero or round away from infinity. + Trunc, + + // 2. Rounding to the nearest integer. + + // Round half toward positive infinity. + HalfCeil, + + // Round half toward negative infinity. + HalfFloor, + + // Round half toward infinity or round half away from zero. + HalfExpand, + + // Round half toward zero or round half away from infinity. + HalfTrunc, + + // Round half to even. + HalfEven, +}; + +/** + * NegateTemporalRoundingMode ( roundingMode ) + */ +constexpr auto NegateTemporalRoundingMode(TemporalRoundingMode roundingMode) { + // Steps 1-5. + switch (roundingMode) { + case TemporalRoundingMode::Ceil: + return TemporalRoundingMode::Floor; + + case TemporalRoundingMode::Floor: + return TemporalRoundingMode::Ceil; + + case TemporalRoundingMode::HalfCeil: + return TemporalRoundingMode::HalfFloor; + + case TemporalRoundingMode::HalfFloor: + return TemporalRoundingMode::HalfCeil; + + case TemporalRoundingMode::Expand: + case TemporalRoundingMode::Trunc: + case TemporalRoundingMode::HalfExpand: + case TemporalRoundingMode::HalfTrunc: + case TemporalRoundingMode::HalfEven: + return roundingMode; + } + MOZ_CRASH("invalid rounding mode"); +} + +/** + * Adjust the rounding mode to round negative values in the same direction as + * positive values. + */ +constexpr auto ToPositiveRoundingMode(TemporalRoundingMode roundingMode) { + switch (roundingMode) { + case TemporalRoundingMode::Ceil: + case TemporalRoundingMode::Floor: + case TemporalRoundingMode::HalfCeil: + case TemporalRoundingMode::HalfFloor: + case TemporalRoundingMode::HalfEven: + // (Half-)Ceil/Floor round toward the same infinity for negative and + // positive values, so the rounding mode doesn't need to be adjusted. The + // same applies for half-even rounding. + return roundingMode; + + case TemporalRoundingMode::Expand: + // Expand rounds positive values toward +infinity, but negative values + // toward -infinity. Adjust the rounding mode to Ceil to round negative + // values in the same direction as positive values. + return TemporalRoundingMode::Ceil; + + case TemporalRoundingMode::Trunc: + // Truncation rounds positive values down toward zero, but negative values + // up toward zero. Adjust the rounding mode to Floor to round negative + // values in the same direction as positive values. + return TemporalRoundingMode::Floor; + + case TemporalRoundingMode::HalfExpand: + // Adjust the rounding mode to Half-Ceil, similar to the Expand case. + return TemporalRoundingMode::HalfCeil; + + case TemporalRoundingMode::HalfTrunc: + // Adjust the rounding mode to Half-Floor, similar to the Trunc case. + return TemporalRoundingMode::HalfFloor; + } + MOZ_CRASH("unexpected rounding mode"); +} + +// Temporal performs division on "mathematical values" [1] with implies using +// infinite precision. This rules out using IEE-754 floating point types like +// `double`. It also means we can't implement the algorithms from the +// specification verbatim, but instead have to translate them into equivalent +// operations. +// +// Throughout the following division functions, the divisor is required to be +// positive. This allows to simplify the implementation, because it ensures +// non-zero quotient and remainder values have the same sign as the dividend. +// +// [1] https://tc39.es/ecma262/#mathematical-value + +/** + * Compute ceiling division ⌈dividend / divisor⌉. The divisor must be a positive + * number. + */ +constexpr int64_t CeilDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % divisor; + + // Ceiling division rounds the quotient toward positive infinity. When the + // quotient is negative, this is equivalent to rounding toward zero. See [1]. + // + // int64_t 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 > 0) { + quotient += 1; + } + return quotient; +} + +/** + * Compute floor division ⌊dividend / divisor⌋. The divisor must be a positive + * number. + */ +constexpr int64_t FloorDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % divisor; + + // Floor division rounds the quotient toward negative infinity. When the + // quotient is positive, this is equivalent to rounding toward zero. See [1]. + // + // int64_t 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 < 0) { + quotient -= 1; + } + return quotient; +} + +/** + * Compute "round toward infinity" division `dividend / divisor`. The divisor + * must be a positive number. + */ +constexpr int64_t ExpandDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % 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 > 0) { + quotient += 1; + } + if (remainder < 0) { + quotient -= 1; + } + return quotient; +} + +/** + * Compute truncating division `dividend / divisor`. The divisor must be a + * positive number. + */ +constexpr int64_t TruncDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // Truncating division rounds both positive and negative quotients toward + // zero, cf. [1]. + // + // int64_t 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 int64_t HalfCeilDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % 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. + // + // int64_t 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 > 0 && uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) { + quotient += 1; + } + if (remainder < 0 && uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { + quotient -= 1; + } + return quotient; +} + +/** + * Compute "round half toward negative infinity" division `dividend / divisor`. + * The divisor must be a positive number. + */ +inline int64_t HalfFloorDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % 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. + // + // int64_t 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 < 0 && uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) { + quotient -= 1; + } + if (remainder > 0 && uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { + quotient += 1; + } + return quotient; +} + +/** + * Compute "round half toward infinity" division `dividend / divisor`. The + * divisor must be a positive number. + */ +inline int64_t HalfExpandDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % 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]. + // + // int64_t 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 (uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) { + quotient += (dividend > 0) ? 1 : -1; + } + return quotient; +} + +/** + * Compute "round half toward zero" division `dividend / divisor`. The divisor + * must be a positive number. + */ +inline int64_t HalfTruncDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % divisor; + + // "Round half toward zero" division rounds both positive and negative + // quotients whose remainder has a fractional part ≤0.5 toward zero. See [1]. + // + // int64_t 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 (uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { + quotient += (dividend > 0) ? 1 : -1; + } + return quotient; +} + +/** + * Compute "round half to even" division `dividend / divisor`. The divisor must + * be a positive number. + */ +inline int64_t HalfEvenDiv(int64_t dividend, int64_t divisor) { + MOZ_ASSERT(divisor > 0, "negative divisor not supported"); + + // NB: Division and modulo operation are fused into a single machine code + // instruction by the compiler. + int64_t quotient = dividend / divisor; + int64_t remainder = dividend % divisor; + + // "Round half to even" division rounds both positive and negative quotients + // to the nearest even integer. See [1]. + // + // int64_t 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 & 1) == 1 && + uint64_t(std::abs(remainder)) * 2 == uint64_t(divisor)) { + quotient += (dividend > 0) ? 1 : -1; + } + if (uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { + quotient += (dividend > 0) ? 1 : -1; + } + return quotient; +} + +/** + * Perform `dividend / divisor` and round the result according to the given + * rounding mode. + */ +inline int64_t Divide(int64_t dividend, int64_t 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 */ -- cgit v1.2.3