summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/TemporalRoundingMode.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/builtin/temporal/TemporalRoundingMode.h
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/temporal/TemporalRoundingMode.h')
-rw-r--r--js/src/builtin/temporal/TemporalRoundingMode.h433
1 files changed, 433 insertions, 0 deletions
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 <cmath>
+#include <stdint.h>
+
+namespace js::temporal {
+
+// Overview of integer rounding modes is available at
+// <https://en.wikipedia.org/wiki/Rounding#Rounding_to_integer>.
+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 */