Skip to content

Commit

Permalink
Implement full set of rounding modes in polyfill
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ptomato committed Sep 21, 2022
1 parent b60cb83 commit d450be1
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
33 changes: 31 additions & 2 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -688,14 +688,23 @@ 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) {
case 'ceil':
return 'floor';
case 'floor':
return 'ceil';
case 'halfCeil':
return 'halfFloor';
case 'halfFloor':
return 'halfCeil';
default:
return roundingMode;
}
Expand Down Expand Up @@ -4197,20 +4206,40 @@ 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);
break;
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);
},
Expand Down
31 changes: 30 additions & 1 deletion polyfill/test/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down

0 comments on commit d450be1

Please sign in to comment.