forked from tc39/test262
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 tc39#3961, so the new tests from this commit start at "precision-exact-mathematical-values-6.js".
- Loading branch information
Showing
3 changed files
with
398 additions
and
0 deletions.
There are no files selected for viewing
140 changes: 140 additions & 0 deletions
140
test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-6.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 changes: 119 additions & 0 deletions
119
test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-7.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}`, | ||
); | ||
} | ||
} | ||
} |
Oops, something went wrong.