-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Normative: Limit time portion of durations to <2⁵³ seconds
In order to avoid unbounded integer arithmetic, we place an upper bound on the total of the time portion of a duration (days through nanoseconds). For the purpose of determining the limits, days are always 24 hours, even if a calendar day may be a different number of hours relative to a particular ZonedDateTime. It's now no longer possible to make Duration.prototype.total() come up with an infinite result; removing the loops in UnbalanceDurationRelative made it so that the duration's calendar units must be able to be added to a relativeTo date without overflowing, and this change makes it so that the duration's time units are too small to overflow to infinity either.
- Loading branch information
Showing
7 changed files
with
154 additions
and
173 deletions.
There are no files selected for viewing
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
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
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,32 @@ | ||
const MathAbs = Math.abs; | ||
const MathLog10 = Math.log10; | ||
const MathSign = Math.sign; | ||
const MathTrunc = Math.trunc; | ||
const NumberParseInt = Number.parseInt; | ||
const NumberPrototypeToPrecision = Number.prototype.toPrecision; | ||
const StringPrototypeSlice = String.prototype.slice; | ||
|
||
import Call from 'es-abstract/2022/Call.js'; | ||
|
||
// Computes trunc(x / 10**p) and x % 10**p, returning { div, mod }, with | ||
// precision loss only once in the quotient, by string manipulation. If the | ||
// quotient and remainder are safe integers, then they are exact. x must be an | ||
// integer. p must be a non-negative integer. Both div and mod have the sign of | ||
// x. | ||
export function TruncatingDivModByPowerOf10(x, p) { | ||
if (x === 0) return { div: x, mod: x }; // preserves signed zero | ||
|
||
const sign = MathSign(x); | ||
x = MathAbs(x); | ||
|
||
const xDigits = MathTrunc(1 + MathLog10(x)); | ||
if (p >= xDigits) return { div: sign * 0, mod: sign * x }; | ||
if (p === 0) return { div: sign * x, mod: sign * 0 }; | ||
|
||
// would perform nearest rounding if x was not an integer: | ||
const xStr = Call(NumberPrototypeToPrecision, x, [xDigits]); | ||
const div = sign * NumberParseInt(Call(StringPrototypeSlice, xStr, [0, xDigits - p]), 10); | ||
const mod = sign * NumberParseInt(Call(StringPrototypeSlice, xStr, [xDigits - p]), 10); | ||
|
||
return { div, mod }; | ||
} |
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
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,69 @@ | ||
import Demitasse from '@pipobscure/demitasse'; | ||
const { describe, it, report } = Demitasse; | ||
|
||
import Pretty from '@pipobscure/demitasse-pretty'; | ||
const { reporter } = Pretty; | ||
|
||
import { strict as assert } from 'assert'; | ||
const { deepEqual } = assert; | ||
|
||
import { TruncatingDivModByPowerOf10 as div } from '../lib/math.mjs'; | ||
|
||
describe('Math', () => { | ||
describe('TruncatingDivModByPowerOf10', () => { | ||
it('12345/10**0 = 12345, 0', () => deepEqual(div(12345, 0), { div: 12345, mod: 0 })); | ||
it('12345/10**1 = 1234, 5', () => deepEqual(div(12345, 1), { div: 1234, mod: 5 })); | ||
it('12345/10**2 = 123, 45', () => deepEqual(div(12345, 2), { div: 123, mod: 45 })); | ||
it('12345/10**3 = 12, 345', () => deepEqual(div(12345, 3), { div: 12, mod: 345 })); | ||
it('12345/10**4 = 1, 2345', () => deepEqual(div(12345, 4), { div: 1, mod: 2345 })); | ||
it('12345/10**5 = 0, 12345', () => deepEqual(div(12345, 5), { div: 0, mod: 12345 })); | ||
it('12345/10**6 = 0, 12345', () => deepEqual(div(12345, 6), { div: 0, mod: 12345 })); | ||
|
||
it('-12345/10**0 = -12345, -0', () => deepEqual(div(-12345, 0), { div: -12345, mod: -0 })); | ||
it('-12345/10**1 = -1234, -5', () => deepEqual(div(-12345, 1), { div: -1234, mod: -5 })); | ||
it('-12345/10**2 = -123, -45', () => deepEqual(div(-12345, 2), { div: -123, mod: -45 })); | ||
it('-12345/10**3 = -12, -345', () => deepEqual(div(-12345, 3), { div: -12, mod: -345 })); | ||
it('-12345/10**4 = -1, -2345', () => deepEqual(div(-12345, 4), { div: -1, mod: -2345 })); | ||
it('-12345/10**5 = -0, -12345', () => deepEqual(div(-12345, 5), { div: -0, mod: -12345 })); | ||
it('-12345/10**6 = -0, -12345', () => deepEqual(div(-12345, 6), { div: -0, mod: -12345 })); | ||
|
||
it('0/10**27 = 0, 0', () => deepEqual(div(0, 27), { div: 0, mod: 0 })); | ||
it('-0/10**27 = -0, -0', () => deepEqual(div(-0, 27), { div: -0, mod: -0 })); | ||
|
||
it('1001/10**3 = 1, 1', () => deepEqual(div(1001, 3), { div: 1, mod: 1 })); | ||
it('-1001/10**3 = -1, -1', () => deepEqual(div(-1001, 3), { div: -1, mod: -1 })); | ||
|
||
it('4019125567429664768/10**3 = 4019125567429664, 768', () => | ||
deepEqual(div(4019125567429664768, 3), { div: 4019125567429664, mod: 768 })); | ||
it('-4019125567429664768/10**3 = -4019125567429664, -768', () => | ||
deepEqual(div(-4019125567429664768, 3), { div: -4019125567429664, mod: -768 })); | ||
it('3294477463410151260160/10**6 = 3294477463410151, 260160', () => | ||
deepEqual(div(3294477463410151260160, 6), { div: 3294477463410151, mod: 260160 })); | ||
it('-3294477463410151260160/10**6 = -3294477463410151, -260160', () => | ||
deepEqual(div(-3294477463410151260160, 6), { div: -3294477463410151, mod: -260160 })); | ||
it('7770017954545649059889152/10**9 = 7770017954545649, 59889152', () => | ||
deepEqual(div(7770017954545649059889152, 9), { div: 7770017954545649, mod: 59889152 })); | ||
it('-7770017954545649059889152/-10**9 = -7770017954545649, -59889152', () => | ||
deepEqual(div(-7770017954545649059889152, 9), { div: -7770017954545649, mod: -59889152 })); | ||
|
||
// Largest/smallest representable float that will result in a safe quotient, | ||
// for each of the divisors 10**3, 10**6, 10**9 | ||
it('9007199254740990976/10**3 = MAX_SAFE_INTEGER-1, 976', () => | ||
deepEqual(div(9007199254740990976, 3), { div: Number.MAX_SAFE_INTEGER - 1, mod: 976 })); | ||
it('-9007199254740990976/10**3 = -MAX_SAFE_INTEGER+1, -976', () => | ||
deepEqual(div(-9007199254740990976, 3), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -976 })); | ||
it('9007199254740990951424/10**6 = MAX_SAFE_INTEGER-1, 951424', () => | ||
deepEqual(div(9007199254740990951424, 6), { div: Number.MAX_SAFE_INTEGER - 1, mod: 951424 })); | ||
it('-9007199254740990951424/10**6 = -MAX_SAFE_INTEGER+1, -951424', () => | ||
deepEqual(div(-9007199254740990951424, 6), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -951424 })); | ||
it('9007199254740990926258176/10**9 = MAX_SAFE_INTEGER-1, 926258176', () => | ||
deepEqual(div(9007199254740990926258176, 9), { div: Number.MAX_SAFE_INTEGER - 1, mod: 926258176 })); | ||
it('-9007199254740990926258176/10**9 = -MAX_SAFE_INTEGER+1, -926258176', () => | ||
deepEqual(div(-9007199254740990926258176, 9), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -926258176 })); | ||
}); | ||
}); | ||
|
||
import { normalize } from 'path'; | ||
if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) { | ||
report(reporter).then((failed) => process.exit(failed ? 1 : 0)); | ||
} |
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
Oops, something went wrong.