From d450be195a81228d284777969bedd9e0475400bd Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Thu, 30 Jun 2022 20:15:22 +0200 Subject: [PATCH] Implement full set of rounding modes in polyfill This brings the reference polyfill up to date with the new rounding modes. It adds a quick set of tests that only test the internal implementation, in order to make sure that the RoundNumberToIncrement operation behaves as intended. Full tests should test the observable outcome in test262. --- polyfill/lib/ecmascript.mjs | 33 +++++++++++++++++++++++++++++++-- polyfill/test/ecmascript.mjs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 9bad2ed4d..886ffc440 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -688,7 +688,12 @@ export const ES = ObjectAssign({}, ES2020, { return ES.GetOption(options, 'disambiguation', ['compatible', 'earlier', 'later', 'reject'], 'compatible'); }, ToTemporalRoundingMode: (options, fallback) => { - return ES.GetOption(options, 'roundingMode', ['ceil', 'floor', 'trunc', 'halfExpand'], fallback); + return ES.GetOption( + options, + 'roundingMode', + ['ceil', 'floor', 'expand', 'trunc', 'halfCeil', 'halfFloor', 'halfExpand', 'halfTrunc', 'halfEven'], + fallback + ); }, NegateTemporalRoundingMode: (roundingMode) => { switch (roundingMode) { @@ -696,6 +701,10 @@ export const ES = ObjectAssign({}, ES2020, { return 'floor'; case 'floor': return 'ceil'; + case 'halfCeil': + return 'halfFloor'; + case 'halfFloor': + return 'halfCeil'; default: return roundingMode; } @@ -4197,6 +4206,9 @@ export const ES = ObjectAssign({}, ES2020, { let { quotient, remainder } = quantity.divmod(increment); if (remainder.equals(bigInt.zero)) return quantity; const sign = remainder.lt(bigInt.zero) ? -1 : 1; + const tiebreaker = remainder.multiply(2).abs(); + const tie = tiebreaker.equals(increment); + const expandIsNearer = tiebreaker.gt(increment); switch (mode) { case 'ceil': if (sign > 0) quotient = quotient.add(sign); @@ -4204,13 +4216,30 @@ export const ES = ObjectAssign({}, ES2020, { case 'floor': if (sign < 0) quotient = quotient.add(sign); break; + case 'expand': + // always expand if there is a remainder + quotient = quotient.add(sign); + break; case 'trunc': // no change needed, because divmod is a truncation break; + case 'halfCeil': + if (expandIsNearer || (tie && sign > 0)) quotient = quotient.add(sign); + break; + case 'halfFloor': + if (expandIsNearer || (tie && sign < 0)) quotient = quotient.add(sign); + break; case 'halfExpand': // "half up away from zero" - if (remainder.multiply(2).abs() >= increment) quotient = quotient.add(sign); + if (expandIsNearer || tie) quotient = quotient.add(sign); break; + case 'halfTrunc': + if (expandIsNearer) quotient = quotient.add(sign); + break; + case 'halfEven': { + if (expandIsNearer || (tie && quotient.isOdd())) quotient = quotient.add(sign); + break; + } } return quotient.multiply(increment); }, diff --git a/polyfill/test/ecmascript.mjs b/polyfill/test/ecmascript.mjs index 6a9588ad7..2c0ff1f58 100644 --- a/polyfill/test/ecmascript.mjs +++ b/polyfill/test/ecmascript.mjs @@ -5,7 +5,9 @@ import Pretty from '@pipobscure/demitasse-pretty'; const { reporter } = Pretty; import { strict as assert } from 'assert'; -const { deepEqual, throws } = assert; +const { deepEqual, equal, throws } = assert; + +import bigInt from 'big-integer'; import { ES } from '../lib/ecmascript.mjs'; import { GetSlot, TIMEZONE_ID } from '../lib/slots.mjs'; @@ -35,6 +37,33 @@ describe('ECMAScript', () => { ); }); }); + + describe('RoundNumberToIncrement', () => { + const increment = bigInt(100); + const testValues = [-150, -100, -80, -50, -30, 0, 30, 50, 80, 100, 150]; + const expectations = { + ceil: [-100, -100, 0, 0, 0, 0, 100, 100, 100, 100, 200], + floor: [-200, -100, -100, -100, -100, 0, 0, 0, 0, 100, 100], + trunc: [-100, -100, 0, 0, 0, 0, 0, 0, 0, 100, 100], + expand: [-200, -100, -100, -100, -100, 0, 100, 100, 100, 100, 200], + halfCeil: [-100, -100, -100, 0, 0, 0, 0, 100, 100, 100, 200], + halfFloor: [-200, -100, -100, -100, 0, 0, 0, 0, 100, 100, 100], + halfTrunc: [-100, -100, -100, 0, 0, 0, 0, 0, 100, 100, 100], + halfExpand: [-200, -100, -100, -100, 0, 0, 0, 100, 100, 100, 200], + halfEven: [-200, -100, -100, 0, 0, 0, 0, 0, 100, 100, 200] + }; + for (const roundingMode of Object.keys(expectations)) { + describe(roundingMode, () => { + testValues.forEach((value, ix) => { + const expected = expectations[roundingMode][ix]; + it(`rounds ${value} to ${expected}`, () => { + const result = ES.RoundNumberToIncrement(bigInt(value), increment, roundingMode); + equal(result.toJSNumber(), expected); + }); + }); + }); + } + }); }); import { normalize } from 'path';