mirror of https://github.com/tc39/test262.git
Add tests for precise results in Duration.p.total and ZonedDateTime.p.hoursInDay
The existing tests didn't cover some edge cases where implementations have to compute the exact result of `numerator / denominator`, where at least one of `numerator` and `denominator` can't be exactly represented by an IEEE-754 double precision floating point value. "precision-exact-mathematical-values-5.js" gets added in #3961, so the new tests from this commit start at "precision-exact-mathematical-values-6.js".
This commit is contained in:
parent
99d5bc8c1b
commit
0de91996e7
140
test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-6.js
vendored
Normal file
140
test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-6.js
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.duration.prototype.total
|
||||
description: >
|
||||
DivideNormalizedTimeDuration computes on exact mathematical values.
|
||||
info: |
|
||||
Temporal.Duration.prototype.total ( totalOf )
|
||||
|
||||
...
|
||||
20. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]],
|
||||
unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], days, norm, 1,
|
||||
unit, "trunc", plainRelativeTo, calendarRec, zonedRelativeTo, timeZoneRec,
|
||||
precalculatedPlainDateTime).
|
||||
21. Return 𝔽(roundRecord.[[Total]]).
|
||||
|
||||
RoundDuration ( ... )
|
||||
|
||||
...
|
||||
14. Else if unit is "hour", then
|
||||
a. Let divisor be 3.6 × 10^12.
|
||||
b. Set total to DivideNormalizedTimeDuration(norm, divisor).
|
||||
...
|
||||
|
||||
DivideNormalizedTimeDuration ( d, divisor )
|
||||
|
||||
1. Assert: divisor ≠ 0.
|
||||
2. Return d.[[TotalNanoseconds]] / divisor.
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
// Randomly generated test data.
|
||||
const data = [
|
||||
{
|
||||
hours: 816,
|
||||
nanoseconds: 2049_187_497_660,
|
||||
},
|
||||
{
|
||||
hours: 7825,
|
||||
nanoseconds: 1865_665_040_770,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 1049_560_584_034,
|
||||
},
|
||||
{
|
||||
hours: 2055144,
|
||||
nanoseconds: 2502_078_444_371,
|
||||
},
|
||||
{
|
||||
hours: 31,
|
||||
nanoseconds: 1010_734_758_745,
|
||||
},
|
||||
{
|
||||
hours: 24,
|
||||
nanoseconds: 2958_999_560_387,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 342_058_521_588,
|
||||
},
|
||||
{
|
||||
hours: 17746,
|
||||
nanoseconds: 3009_093_506_309,
|
||||
},
|
||||
{
|
||||
hours: 4,
|
||||
nanoseconds: 892_480_914_569,
|
||||
},
|
||||
{
|
||||
hours: 3954,
|
||||
nanoseconds: 571_647_777_618,
|
||||
},
|
||||
{
|
||||
hours: 27,
|
||||
nanoseconds: 2322_199_502_640,
|
||||
},
|
||||
{
|
||||
hours: 258054064,
|
||||
nanoseconds: 2782_411_891_222,
|
||||
},
|
||||
{
|
||||
hours: 1485,
|
||||
nanoseconds: 2422_559_903_100,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 1461_068_214_153,
|
||||
},
|
||||
{
|
||||
hours: 393,
|
||||
nanoseconds: 1250_229_561_658,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 91_035_820,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 790_982_655,
|
||||
},
|
||||
{
|
||||
hours: 150,
|
||||
nanoseconds: 608_531_524,
|
||||
},
|
||||
{
|
||||
hours: 5469,
|
||||
nanoseconds: 889_204_952,
|
||||
},
|
||||
{
|
||||
hours: 7870,
|
||||
nanoseconds: 680_042_770,
|
||||
},
|
||||
];
|
||||
|
||||
const nsPerHour = 3600_000_000_000;
|
||||
|
||||
const fractionDigits = Math.log10(nsPerHour) + Math.log10(100_000_000_000) - Math.log10(36);
|
||||
assert.sameValue(fractionDigits, 22);
|
||||
|
||||
for (let {hours, nanoseconds} of data) {
|
||||
assert(nanoseconds < nsPerHour);
|
||||
|
||||
// Compute enough fractional digits to approximate the exact result. Use BigInts
|
||||
// to avoid floating point precision loss. Fill to the left with implicit zeros.
|
||||
let fraction = ((BigInt(nanoseconds) * 100_000_000_000n) / 36n).toString().padStart(fractionDigits, "0");
|
||||
|
||||
// Get the Number approximation from the string representation.
|
||||
let expected = Number(`${hours}.${fraction}`);
|
||||
|
||||
let d = Temporal.Duration.from({hours, nanoseconds});
|
||||
let actual = d.total("hours");
|
||||
|
||||
assert.sameValue(
|
||||
actual,
|
||||
expected,
|
||||
`hours=${hours}, nanoseconds=${nanoseconds}`,
|
||||
);
|
||||
}
|
119
test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-7.js
vendored
Normal file
119
test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-7.js
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-temporal.duration.prototype.total
|
||||
description: >
|
||||
DivideNormalizedTimeDuration computes on exact mathematical values.
|
||||
info: |
|
||||
Temporal.Duration.prototype.total ( totalOf )
|
||||
|
||||
...
|
||||
20. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]],
|
||||
unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], days, norm, 1,
|
||||
unit, "trunc", plainRelativeTo, calendarRec, zonedRelativeTo, timeZoneRec,
|
||||
precalculatedPlainDateTime).
|
||||
21. Return 𝔽(roundRecord.[[Total]]).
|
||||
|
||||
RoundDuration ( ... )
|
||||
|
||||
...
|
||||
16. Else if unit is "second", then
|
||||
a. Let divisor be 10^9.
|
||||
b. Set total to DivideNormalizedTimeDuration(norm, divisor).
|
||||
...
|
||||
17. Else if unit is "millisecond", then
|
||||
a. Let divisor be 10^6.
|
||||
b. Set total to DivideNormalizedTimeDuration(norm, divisor).
|
||||
...
|
||||
18. Else if unit is "microsecond", then
|
||||
a. Let divisor be 10^3.
|
||||
b. Set total to DivideNormalizedTimeDuration(norm, divisor).
|
||||
...
|
||||
|
||||
DivideNormalizedTimeDuration ( d, divisor )
|
||||
|
||||
1. Assert: divisor ≠ 0.
|
||||
2. Return d.[[TotalNanoseconds]] / divisor.
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
// Test duration units where the fractional part is a power of ten.
|
||||
const units = [
|
||||
"seconds", "milliseconds", "microseconds", "nanoseconds",
|
||||
];
|
||||
|
||||
// Conversion factors to nanoseconds precision.
|
||||
const toNanos = {
|
||||
"seconds": 1_000_000_000n,
|
||||
"milliseconds": 1_000_000n,
|
||||
"microseconds": 1_000n,
|
||||
"nanoseconds": 1n,
|
||||
};
|
||||
|
||||
const integers = [
|
||||
// Small integers.
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
|
||||
// Large integers around Number.MAX_SAFE_INTEGER.
|
||||
2**51,
|
||||
2**52,
|
||||
2**53,
|
||||
2**54,
|
||||
];
|
||||
|
||||
const fractions = [
|
||||
// True fractions.
|
||||
0, 1, 10, 100, 125, 200, 250, 500, 750, 800, 900, 950, 999,
|
||||
|
||||
// Fractions with overflow.
|
||||
1_000,
|
||||
1_999,
|
||||
2_000,
|
||||
2_999,
|
||||
3_000,
|
||||
3_999,
|
||||
4_000,
|
||||
4_999,
|
||||
|
||||
999_999,
|
||||
1_000_000,
|
||||
1_000_001,
|
||||
|
||||
999_999_999,
|
||||
1_000_000_000,
|
||||
1_000_000_001,
|
||||
];
|
||||
|
||||
const maxTimeDuration = (2n ** 53n) * (10n ** 9n) - 1n;
|
||||
|
||||
// Iterate over all units except the last one.
|
||||
for (let unit of units.slice(0, -1)) {
|
||||
let smallerUnit = units[units.indexOf(unit) + 1];
|
||||
|
||||
for (let integer of integers) {
|
||||
for (let fraction of fractions) {
|
||||
// Total nanoseconds must not exceed |maxTimeDuration|.
|
||||
let totalNanoseconds = BigInt(integer) * toNanos[unit] + BigInt(fraction) * toNanos[smallerUnit];
|
||||
if (totalNanoseconds > maxTimeDuration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the Number approximation from the string representation.
|
||||
let i = BigInt(integer) + BigInt(fraction) / 1000n;
|
||||
let f = String(fraction % 1000).padStart(3, "0");
|
||||
let expected = Number(`${i}.${f}`);
|
||||
|
||||
let d = Temporal.Duration.from({[unit]: integer, [smallerUnit]: fraction});
|
||||
let actual = d.total(unit);
|
||||
|
||||
assert.sameValue(
|
||||
actual,
|
||||
expected,
|
||||
`${unit}=${integer}, ${smallerUnit}=${fraction}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
139
test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/precision-exact-mathematical-values-2.js
vendored
Normal file
139
test/built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/precision-exact-mathematical-values-2.js
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
// Copyright (C) 2024 André Bargull. All rights reserved.
|
||||
// This code is governed by the BSD license found in the LICENSE file.
|
||||
|
||||
/*---
|
||||
esid: sec-get-temporal.zoneddatetime.prototype.hoursinday
|
||||
description: >
|
||||
Hours in day is correctly rounded using precise mathematical values.
|
||||
info: |
|
||||
get Temporal.ZonedDateTime.prototype.hoursInDay
|
||||
|
||||
...
|
||||
14. Let diffNs be tomorrowInstant.[[Nanoseconds]] - todayInstant.[[Nanoseconds]].
|
||||
15. Return 𝔽(diffNs / (3.6 × 10^12)).
|
||||
features: [Temporal]
|
||||
---*/
|
||||
|
||||
// Randomly generated test data.
|
||||
const data = [
|
||||
{
|
||||
hours: 816,
|
||||
nanoseconds: 2049_187_497_660,
|
||||
},
|
||||
{
|
||||
hours: 7825,
|
||||
nanoseconds: 1865_665_040_770,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 1049_560_584_034,
|
||||
},
|
||||
{
|
||||
hours: 2055144,
|
||||
nanoseconds: 2502_078_444_371,
|
||||
},
|
||||
{
|
||||
hours: 31,
|
||||
nanoseconds: 1010_734_758_745,
|
||||
},
|
||||
{
|
||||
hours: 24,
|
||||
nanoseconds: 2958_999_560_387,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 342_058_521_588,
|
||||
},
|
||||
{
|
||||
hours: 17746,
|
||||
nanoseconds: 3009_093_506_309,
|
||||
},
|
||||
{
|
||||
hours: 4,
|
||||
nanoseconds: 892_480_914_569,
|
||||
},
|
||||
{
|
||||
hours: 3954,
|
||||
nanoseconds: 571_647_777_618,
|
||||
},
|
||||
{
|
||||
hours: 27,
|
||||
nanoseconds: 2322_199_502_640,
|
||||
},
|
||||
{
|
||||
hours: 258054064,
|
||||
nanoseconds: 2782_411_891_222,
|
||||
},
|
||||
{
|
||||
hours: 1485,
|
||||
nanoseconds: 2422_559_903_100,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 1461_068_214_153,
|
||||
},
|
||||
{
|
||||
hours: 393,
|
||||
nanoseconds: 1250_229_561_658,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 91_035_820,
|
||||
},
|
||||
{
|
||||
hours: 0,
|
||||
nanoseconds: 790_982_655,
|
||||
},
|
||||
{
|
||||
hours: 150,
|
||||
nanoseconds: 608_531_524,
|
||||
},
|
||||
{
|
||||
hours: 5469,
|
||||
nanoseconds: 889_204_952,
|
||||
},
|
||||
{
|
||||
hours: 7870,
|
||||
nanoseconds: 680_042_770,
|
||||
},
|
||||
];
|
||||
|
||||
const nsPerHour = 3600_000_000_000;
|
||||
|
||||
const fractionDigits = Math.log10(nsPerHour) + Math.log10(100_000_000_000) - Math.log10(36);
|
||||
assert.sameValue(fractionDigits, 22);
|
||||
|
||||
for (let {hours, nanoseconds} of data) {
|
||||
assert(nanoseconds < nsPerHour);
|
||||
|
||||
// Compute enough fractional digits to approximate the exact result. Use BigInts
|
||||
// to avoid floating point precision loss. Fill to the left with implicit zeros.
|
||||
let fraction = ((BigInt(nanoseconds) * 100_000_000_000n) / 36n).toString().padStart(fractionDigits, "0");
|
||||
|
||||
// Get the Number approximation from the string representation.
|
||||
let expected = Number(`${hours}.${fraction}`);
|
||||
|
||||
let todayInstant = 0n;
|
||||
let tomorrowInstant = BigInt(hours) * BigInt(nsPerHour) + BigInt(nanoseconds);
|
||||
|
||||
let timeZone = new class extends Temporal.TimeZone {
|
||||
#getPossibleInstantsFor = 0;
|
||||
|
||||
getPossibleInstantsFor() {
|
||||
if (++this.#getPossibleInstantsFor === 1) {
|
||||
return [new Temporal.Instant(todayInstant)];
|
||||
}
|
||||
assert.sameValue(this.#getPossibleInstantsFor, 2);
|
||||
return [new Temporal.Instant(tomorrowInstant)];
|
||||
}
|
||||
}("UTC");
|
||||
|
||||
let zdt = new Temporal.ZonedDateTime(0n, timeZone);
|
||||
let actual = zdt.hoursInDay;
|
||||
|
||||
assert.sameValue(
|
||||
actual,
|
||||
expected,
|
||||
`hours=${hours}, nanoseconds=${nanoseconds}`,
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue