Skip to content

Commit a907acf

Browse files
ptomato12wrigja
authored andcommitted
Avoid rounding errors in (Un)balanceDurationRelative
According to the spec, the arithmetic in BalanceDurationRelative and UnbalanceDurationRelative must not be performed in the floating-point domain. Anba wrote some tests that verify this. UPSTREAM_COMMIT=0d136e3be21301db9489287ae674419f59eae03c
1 parent 08f37ed commit a907acf

File tree

2 files changed

+81
-61
lines changed

2 files changed

+81
-61
lines changed

lib/ecmascript.ts

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ function assertExists<A>(arg: A): asserts arg is NonNullable<A> {
145145
}
146146
}
147147

148+
function isZero(value: JSBI): boolean {
149+
return JSBI.equal(value, ZERO);
150+
}
151+
148152
function IsInteger(value: unknown): value is number {
149153
if (typeof value !== 'number' || !NumberIsFinite(value)) return false;
150154
const abs = MathAbs(value);
@@ -3591,13 +3595,20 @@ export function UnbalanceDurationRelative(
35913595
daysParam: number,
35923596
largestUnit: Temporal.DateTimeUnit,
35933597
relativeToParam: ReturnType<typeof ToRelativeTemporalObject>
3594-
) {
3595-
let years = yearsParam;
3596-
let months = monthsParam;
3597-
let weeks = weeksParam;
3598-
let days = daysParam;
3598+
): {
3599+
years: number;
3600+
months: number;
3601+
weeks: number;
3602+
days: number;
3603+
} {
35993604
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
3600-
const sign = DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
3605+
const sign = DurationSign(yearsParam, monthsParam, weeksParam, daysParam, 0, 0, 0, 0, 0, 0);
3606+
const signBI = JSBI.BigInt(sign);
3607+
3608+
let years = JSBI.BigInt(yearsParam);
3609+
let months = JSBI.BigInt(monthsParam);
3610+
let weeks = JSBI.BigInt(weeksParam);
3611+
let days = JSBI.BigInt(daysParam);
36013612

36023613
let calendar;
36033614
let relativeTo: Temporal.PlainDate | undefined;
@@ -3621,76 +3632,80 @@ export function UnbalanceDurationRelative(
36213632
// balance years down to months
36223633
const dateAdd = calendar.dateAdd;
36233634
const dateUntil = calendar.dateUntil;
3624-
while (MathAbs(years) > 0) {
3635+
while (!isZero(abs(years))) {
36253636
const newRelativeTo = CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd);
36263637
const untilOptions = ObjectCreate(null);
36273638
untilOptions.largestUnit = 'month';
36283639
const untilResult = CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil);
3629-
const oneYearMonths = GetSlot(untilResult, MONTHS);
3640+
const oneYearMonths = JSBI.BigInt(GetSlot(untilResult, MONTHS));
36303641
relativeTo = newRelativeTo;
3631-
months += oneYearMonths;
3632-
years -= sign;
3642+
months = JSBI.add(months, oneYearMonths);
3643+
years = JSBI.subtract(years, signBI);
36333644
}
36343645
}
36353646
break;
3636-
case 'week':
3647+
case 'week': {
36373648
if (!calendar) throw new RangeError('a starting point is required for weeks balancing');
36383649
assertExists(relativeTo);
36393650
const dateAdd = calendar.dateAdd;
36403651
// balance years down to days
3641-
while (MathAbs(years) > 0) {
3652+
// balance years down to days
3653+
while (!isZero(abs(years))) {
36423654
let oneYearDays;
36433655
({ relativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
3644-
days += oneYearDays;
3645-
years -= sign;
3656+
days = JSBI.add(days, JSBI.BigInt(oneYearDays));
3657+
years = JSBI.subtract(years, signBI);
36463658
}
36473659

36483660
// balance months down to days
3649-
while (MathAbs(months) > 0) {
3661+
while (!isZero(abs(months))) {
36503662
let oneMonthDays;
36513663
({ relativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
3652-
days += oneMonthDays;
3653-
months -= sign;
3664+
days = JSBI.add(days, JSBI.BigInt(oneMonthDays));
3665+
months = JSBI.subtract(months, signBI);
36543666
}
36553667
break;
3668+
}
36563669
default: {
36573670
// balance years down to days
3658-
if (years == 0 && months == 0 && weeks == 0) break;
3671+
if (isZero(years) && isZero(months) && isZero(weeks)) break;
36593672
if (!calendar) throw new RangeError('a starting point is required for balancing calendar units');
36603673
const dateAdd = calendar.dateAdd;
3661-
while (MathAbs(years) > 0) {
3662-
if (!calendar) throw new RangeError('a starting point is required for balancing calendar units');
3674+
while (!isZero(abs(years))) {
36633675
assertExists(relativeTo);
36643676
let oneYearDays;
36653677
({ relativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
3666-
days += oneYearDays;
3667-
years -= sign;
3678+
days = JSBI.add(days, JSBI.BigInt(oneYearDays));
3679+
years = JSBI.subtract(years, signBI);
36683680
}
36693681

36703682
// balance months down to days
3671-
while (MathAbs(months) > 0) {
3672-
if (!calendar) throw new RangeError('a starting point is required for balancing calendar units');
3683+
while (!isZero(abs(months))) {
36733684
assertExists(relativeTo);
36743685
let oneMonthDays;
36753686
({ relativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
3676-
days += oneMonthDays;
3677-
months -= sign;
3687+
days = JSBI.add(days, JSBI.BigInt(oneMonthDays));
3688+
months = JSBI.subtract(months, signBI);
36783689
}
36793690

36803691
// balance weeks down to days
3681-
while (MathAbs(weeks) > 0) {
3682-
if (!calendar) throw new RangeError('a starting point is required for balancing calendar units');
3692+
while (!isZero(abs(weeks))) {
36833693
assertExists(relativeTo);
36843694
let oneWeekDays;
36853695
({ relativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
3686-
days += oneWeekDays;
3687-
weeks -= sign;
3696+
days = JSBI.add(days, JSBI.BigInt(oneWeekDays));
3697+
weeks = JSBI.subtract(weeks, signBI);
36883698
}
36893699
break;
36903700
}
36913701
}
36923702

3693-
return { years, months, weeks, days };
3703+
return {
3704+
years: JSBI.toNumber(years),
3705+
months: JSBI.toNumber(months),
3706+
weeks: JSBI.toNumber(weeks),
3707+
days: JSBI.toNumber(days)
3708+
};
36943709
}
36953710

36963711
export function BalanceDurationRelative(
@@ -3700,14 +3715,21 @@ export function BalanceDurationRelative(
37003715
daysParam: number,
37013716
largestUnit: Temporal.DateTimeUnit,
37023717
relativeToParam: ReturnType<typeof ToRelativeTemporalObject>
3703-
) {
3704-
let years = yearsParam;
3705-
let months = monthsParam;
3706-
let weeks = weeksParam;
3707-
let days = daysParam;
3718+
): {
3719+
years: number;
3720+
months: number;
3721+
weeks: number;
3722+
days: number;
3723+
} {
37083724
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
3709-
const sign = DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
3710-
if (sign === 0) return { years, months, weeks, days };
3725+
const sign = DurationSign(yearsParam, monthsParam, weeksParam, daysParam, 0, 0, 0, 0, 0, 0);
3726+
if (sign === 0) return { years: yearsParam, months: monthsParam, weeks: weeksParam, days: daysParam };
3727+
const signBI = JSBI.BigInt(sign);
3728+
3729+
let years = JSBI.BigInt(yearsParam);
3730+
let months = JSBI.BigInt(monthsParam);
3731+
let weeks = JSBI.BigInt(weeksParam);
3732+
let days = JSBI.BigInt(daysParam);
37113733

37123734
let calendar;
37133735
let relativeTo: Temporal.PlainDate | undefined;
@@ -3728,19 +3750,19 @@ export function BalanceDurationRelative(
37283750
// balance days up to years
37293751
let newRelativeTo, oneYearDays;
37303752
({ relativeTo: newRelativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
3731-
while (MathAbs(days) >= MathAbs(oneYearDays)) {
3732-
days -= oneYearDays;
3733-
years += sign;
3753+
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneYearDays)))) {
3754+
days = JSBI.subtract(days, JSBI.BigInt(oneYearDays));
3755+
years = JSBI.add(years, signBI);
37343756
relativeTo = newRelativeTo;
37353757
({ relativeTo: newRelativeTo, days: oneYearDays } = MoveRelativeDate(calendar, relativeTo, oneYear, dateAdd));
37363758
}
37373759

37383760
// balance days up to months
37393761
let oneMonthDays;
37403762
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
3741-
while (MathAbs(days) >= MathAbs(oneMonthDays)) {
3742-
days -= oneMonthDays;
3743-
months += sign;
3763+
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneMonthDays)))) {
3764+
days = JSBI.subtract(days, JSBI.BigInt(oneMonthDays));
3765+
months = JSBI.add(months, signBI);
37443766
relativeTo = newRelativeTo;
37453767
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
37463768
}
@@ -3752,9 +3774,9 @@ export function BalanceDurationRelative(
37523774
untilOptions.largestUnit = 'month';
37533775
let untilResult = CalendarDateUntil(calendar, relativeTo, newRelativeTo, untilOptions, dateUntil);
37543776
let oneYearMonths = GetSlot(untilResult, MONTHS);
3755-
while (MathAbs(months) >= MathAbs(oneYearMonths)) {
3756-
months -= oneYearMonths;
3757-
years += sign;
3777+
while (JSBI.greaterThanOrEqual(abs(months), JSBI.BigInt(MathAbs(oneYearMonths)))) {
3778+
months = JSBI.subtract(months, JSBI.BigInt(oneYearMonths));
3779+
years = JSBI.add(years, signBI);
37583780
relativeTo = newRelativeTo;
37593781
newRelativeTo = CalendarDateAdd(calendar, relativeTo, oneYear, undefined, dateAdd);
37603782
const untilOptions = ObjectCreate(null);
@@ -3771,9 +3793,9 @@ export function BalanceDurationRelative(
37713793
// balance days up to months
37723794
let newRelativeTo, oneMonthDays;
37733795
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
3774-
while (MathAbs(days) >= MathAbs(oneMonthDays)) {
3775-
days -= oneMonthDays;
3776-
months += sign;
3796+
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneMonthDays)))) {
3797+
days = JSBI.subtract(days, JSBI.BigInt(oneMonthDays));
3798+
months = JSBI.add(months, signBI);
37773799
relativeTo = newRelativeTo;
37783800
({ relativeTo: newRelativeTo, days: oneMonthDays } = MoveRelativeDate(calendar, relativeTo, oneMonth, dateAdd));
37793801
}
@@ -3786,9 +3808,9 @@ export function BalanceDurationRelative(
37863808
// balance days up to weeks
37873809
let newRelativeTo, oneWeekDays;
37883810
({ relativeTo: newRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
3789-
while (MathAbs(days) >= MathAbs(oneWeekDays)) {
3790-
days -= oneWeekDays;
3791-
weeks += sign;
3811+
while (JSBI.greaterThanOrEqual(abs(days), JSBI.BigInt(MathAbs(oneWeekDays)))) {
3812+
days = JSBI.subtract(days, JSBI.BigInt(oneWeekDays));
3813+
weeks = JSBI.add(weeks, signBI);
37923814
relativeTo = newRelativeTo;
37933815
({ relativeTo: newRelativeTo, days: oneWeekDays } = MoveRelativeDate(calendar, relativeTo, oneWeek, dateAdd));
37943816
}
@@ -3799,7 +3821,12 @@ export function BalanceDurationRelative(
37993821
break;
38003822
}
38013823

3802-
return { years, months, weeks, days };
3824+
return {
3825+
years: JSBI.toNumber(years),
3826+
months: JSBI.toNumber(months),
3827+
weeks: JSBI.toNumber(weeks),
3828+
days: JSBI.toNumber(days)
3829+
};
38033830
}
38043831

38053832
export function CalculateOffsetShift(

test/expected-failures-todo-migrated-code.txt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,6 @@ built-ins/Temporal/Calendar/prototype/yearOfWeek/not-a-constructor.js
146146
built-ins/Temporal/Calendar/prototype/yearOfWeek/prop-desc.js
147147
built-ins/Temporal/Calendar/prototype/yearOfWeek/year-zero.js
148148
built-ins/Temporal/Duration/compare/order-of-operations.js
149-
built-ins/Temporal/Duration/compare/precision-exact-mathematical-values-1.js
150-
built-ins/Temporal/Duration/compare/precision-exact-mathematical-values-2.js
151149
built-ins/Temporal/Duration/compare/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js
152150
built-ins/Temporal/Duration/compare/relativeto-propertybag-timezone-wrong-type.js
153151
built-ins/Temporal/Duration/from/argument-string-fractional-precision.js
@@ -165,11 +163,6 @@ built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-loop-indefinitel
165163
built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-precision-exact-mathematical-values-1.js
166164
built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-precision-exact-mathematical-values-2.js
167165
built-ins/Temporal/Duration/prototype/round/order-of-operations.js
168-
built-ins/Temporal/Duration/prototype/round/precision-exact-in-balance-duration-relative-months.js
169-
built-ins/Temporal/Duration/prototype/round/precision-exact-in-balance-duration-relative-weeks.js
170-
built-ins/Temporal/Duration/prototype/round/precision-exact-in-balance-duration-relative-years-days.js
171-
built-ins/Temporal/Duration/prototype/round/precision-exact-in-balance-duration-relative-years-months.js
172-
built-ins/Temporal/Duration/prototype/round/precision-exact-in-balance-duration-relative-years-with-calendar.js
173166
built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-calendar-fields-undefined.js
174167
built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-calendar-wrong-type.js
175168
built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js

0 commit comments

Comments
 (0)