зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1874684 - Part 10: Replace BigInt with Int128 in RoundNumberToIncrement. r=mgaudet
Use `Int128` instead of `BigInt` in RoundNumberToIncrement to avoid duplicated code for the `BigInt` code path. Differential Revision: https://phabricator.services.mozilla.com/D198544
This commit is contained in:
Родитель
d32aed0104
Коммит
5636d9f394
|
@ -3045,17 +3045,21 @@ static bool CreateCalendarMethodsRecordFromRelativeTo(
|
|||
return true;
|
||||
}
|
||||
|
||||
struct RoundedNumber final {
|
||||
Int128 rounded;
|
||||
double total;
|
||||
};
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
static void TruncateNumber(int64_t numerator, int64_t denominator,
|
||||
double* quotient, double* total) {
|
||||
static RoundedNumber TruncateNumber(int64_t numerator, int64_t denominator) {
|
||||
// Computes the quotient and real number value of the rational number
|
||||
// |numerator / denominator|.
|
||||
|
||||
// Int64 division truncates.
|
||||
int64_t q = numerator / denominator;
|
||||
int64_t r = numerator % denominator;
|
||||
int64_t quot = numerator / denominator;
|
||||
int64_t rem = numerator % denominator;
|
||||
|
||||
// The total value is stored as a mathematical number in the draft proposal,
|
||||
// so we can't convert it to a double without loss of precision. We use two
|
||||
|
@ -3074,49 +3078,29 @@ static void TruncateNumber(int64_t numerator, int64_t denominator,
|
|||
//
|
||||
// The best possible approximation is |4000.0000000000004547...𝔽|, which can
|
||||
// be computed through |q + r / denominator|.
|
||||
double total;
|
||||
if (::IsSafeInteger(numerator) && ::IsSafeInteger(denominator)) {
|
||||
*quotient = double(q);
|
||||
*total = double(numerator) / double(denominator);
|
||||
total = double(numerator) / double(denominator);
|
||||
} else {
|
||||
*quotient = double(q);
|
||||
*total = double(q) + double(r) / double(denominator);
|
||||
total = double(quot) + double(rem) / double(denominator);
|
||||
}
|
||||
return {Int128{quot}, total};
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
static bool TruncateNumber(JSContext* cx, Handle<BigInt*> numerator,
|
||||
Handle<BigInt*> denominator, double* quotient,
|
||||
double* total) {
|
||||
MOZ_ASSERT(!denominator->isNegative());
|
||||
MOZ_ASSERT(!denominator->isZero());
|
||||
static RoundedNumber TruncateNumber(const Int128& numerator,
|
||||
const Int128& denominator) {
|
||||
MOZ_ASSERT(denominator > Int128{});
|
||||
MOZ_ASSERT(numerator > Int128{INT64_MAX} || denominator > Int128{INT64_MAX},
|
||||
"small values use the int64 overload");
|
||||
|
||||
// Dividing zero is always zero.
|
||||
if (numerator->isZero()) {
|
||||
*quotient = 0;
|
||||
*total = 0;
|
||||
return true;
|
||||
}
|
||||
// Int128 division truncates.
|
||||
auto [quot, rem] = numerator.divrem(denominator);
|
||||
|
||||
int64_t num, denom;
|
||||
if (BigInt::isInt64(numerator, &num) &&
|
||||
BigInt::isInt64(denominator, &denom)) {
|
||||
TruncateNumber(num, denom, quotient, total);
|
||||
return true;
|
||||
}
|
||||
|
||||
// BigInt division truncates.
|
||||
Rooted<BigInt*> quot(cx);
|
||||
Rooted<BigInt*> rem(cx);
|
||||
if (!BigInt::divmod(cx, numerator, denominator, ", &rem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double q = BigInt::numberValue(quot);
|
||||
*quotient = q;
|
||||
*total = q + BigInt::numberValue(rem) / BigInt::numberValue(denominator);
|
||||
return true;
|
||||
double total = double(quot) + double(rem) / double(denominator);
|
||||
return {quot, total};
|
||||
}
|
||||
|
||||
struct RoundedDuration final {
|
||||
|
@ -3264,139 +3248,31 @@ static bool DaysIsNegative(int64_t days,
|
|||
return totalDays < 0 || (totalDays == 0 && timeAndDays.time < 0);
|
||||
}
|
||||
|
||||
struct RoundedNumber {
|
||||
double rounded;
|
||||
double total;
|
||||
};
|
||||
static RoundedNumber RoundNumberToIncrement(
|
||||
int64_t durationAmount, int64_t amountPassed, int64_t durationDays,
|
||||
int32_t daysToAdd, const NormalizedTimeAndDays& timeAndDays,
|
||||
int32_t oneUnitDays, Increment increment, TemporalRoundingMode roundingMode,
|
||||
ComputeRemainder computeRemainder) {
|
||||
#ifdef DEBUG
|
||||
// Valid duration days are smaller than ⌈(2**53) / (24 * 60 * 60)⌉.
|
||||
static constexpr int64_t maxDurationDays =
|
||||
(int64_t(1) << 53) / (24 * 60 * 60);
|
||||
|
||||
static bool RoundNumberToIncrementSlow(JSContext* cx, double durationAmount,
|
||||
double amountPassed, double durationDays,
|
||||
int32_t daysToAdd,
|
||||
const NormalizedTimeAndDays& timeAndDays,
|
||||
int32_t oneUnitDays, Increment increment,
|
||||
TemporalRoundingMode roundingMode,
|
||||
ComputeRemainder computeRemainder,
|
||||
RoundedNumber* result) {
|
||||
// Numbers of days between nsMinInstant and nsMaxInstant.
|
||||
static constexpr int32_t epochDays = 200'000'000;
|
||||
#endif
|
||||
|
||||
MOZ_ASSERT(std::abs(durationAmount) < (int64_t(1) << 32));
|
||||
MOZ_ASSERT(std::abs(amountPassed) < (int64_t(1) << 32));
|
||||
MOZ_ASSERT(std::abs(durationDays) <= maxDurationDays);
|
||||
MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
|
||||
MOZ_ASSERT(timeAndDays.dayLength > 0);
|
||||
MOZ_ASSERT(timeAndDays.dayLength < (int64_t(1) << 53));
|
||||
MOZ_ASSERT(std::abs(timeAndDays.time) < timeAndDays.dayLength);
|
||||
MOZ_ASSERT(std::abs(timeAndDays.days) <= maxDurationDays);
|
||||
MOZ_ASSERT(oneUnitDays != 0);
|
||||
|
||||
Rooted<BigInt*> biAmount(cx, BigInt::createFromDouble(cx, durationAmount));
|
||||
if (!biAmount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> biAmountPassed(cx,
|
||||
BigInt::createFromDouble(cx, amountPassed));
|
||||
if (!biAmountPassed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
biAmount = BigInt::add(cx, biAmount, biAmountPassed);
|
||||
if (!biAmount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> days(cx, BigInt::createFromDouble(cx, durationDays));
|
||||
if (!days) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> nanoDays(cx, BigInt::createFromInt64(cx, timeAndDays.days));
|
||||
if (!nanoDays) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> biDaysToAdd(cx, BigInt::createFromInt64(cx, daysToAdd));
|
||||
if (!biDaysToAdd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
days = BigInt::add(cx, days, nanoDays);
|
||||
if (!days) {
|
||||
return false;
|
||||
}
|
||||
|
||||
days = BigInt::add(cx, days, biDaysToAdd);
|
||||
if (!days) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> nanoseconds(cx,
|
||||
BigInt::createFromInt64(cx, timeAndDays.time));
|
||||
if (!nanoseconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> dayLength(cx,
|
||||
BigInt::createFromInt64(cx, timeAndDays.dayLength));
|
||||
if (!dayLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> denominator(
|
||||
cx, BigInt::createFromInt64(cx, std::abs(oneUnitDays)));
|
||||
if (!denominator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
denominator = BigInt::mul(cx, denominator, dayLength);
|
||||
if (!denominator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> totalNanoseconds(cx, BigInt::mul(cx, days, dayLength));
|
||||
if (!totalNanoseconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
totalNanoseconds = BigInt::add(cx, totalNanoseconds, nanoseconds);
|
||||
if (!totalNanoseconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> amountNanos(cx, BigInt::mul(cx, biAmount, denominator));
|
||||
if (!amountNanos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
totalNanoseconds = BigInt::add(cx, totalNanoseconds, amountNanos);
|
||||
if (!totalNanoseconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double rounded;
|
||||
double total = 0;
|
||||
if (computeRemainder == ComputeRemainder::No) {
|
||||
if (!temporal::RoundNumberToIncrement(cx, totalNanoseconds, denominator,
|
||||
increment, roundingMode, &rounded)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!::TruncateNumber(cx, totalNanoseconds, denominator, &rounded,
|
||||
&total)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*result = {rounded, total};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool RoundNumberToIncrement(JSContext* cx, double durationAmount,
|
||||
double amountPassed, double durationDays,
|
||||
int32_t daysToAdd,
|
||||
const NormalizedTimeAndDays& timeAndDays,
|
||||
int32_t oneUnitDays, Increment increment,
|
||||
TemporalRoundingMode roundingMode,
|
||||
ComputeRemainder computeRemainder,
|
||||
RoundedNumber* result) {
|
||||
MOZ_ASSERT(timeAndDays.dayLength > 0);
|
||||
MOZ_ASSERT(std::abs(timeAndDays.time) < timeAndDays.dayLength);
|
||||
MOZ_ASSERT(oneUnitDays != 0);
|
||||
MOZ_ASSERT(std::abs(oneUnitDays) <= 200'000'000);
|
||||
|
||||
// TODO(anba): Rename variables.
|
||||
MOZ_ASSERT(std::abs(oneUnitDays) <= epochDays);
|
||||
MOZ_ASSERT(increment <= Increment::max());
|
||||
|
||||
// clang-format off
|
||||
//
|
||||
|
@ -3430,89 +3306,117 @@ static bool RoundNumberToIncrement(JSContext* cx, double durationAmount,
|
|||
break;
|
||||
}
|
||||
|
||||
int64_t intDays;
|
||||
if (!mozilla::NumberEqualsInt64(durationDays, &intDays)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto totalDays = mozilla::CheckedInt64(intDays);
|
||||
auto totalDays = mozilla::CheckedInt64(durationDays);
|
||||
totalDays += timeAndDays.days;
|
||||
totalDays += daysToAdd;
|
||||
if (!totalDays.isValid()) {
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(totalDays.isValid());
|
||||
|
||||
auto totalNanoseconds = dayLength * totalDays;
|
||||
if (!totalNanoseconds.isValid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
totalNanoseconds += timeAndDays.time;
|
||||
if (!totalNanoseconds.isValid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int64_t intAmount;
|
||||
if (!mozilla::NumberEqualsInt64(durationAmount, &intAmount)) {
|
||||
break;
|
||||
}
|
||||
|
||||
int64_t intAmountPassed;
|
||||
if (!mozilla::NumberEqualsInt64(amountPassed, &intAmountPassed)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto totalAmount = mozilla::CheckedInt64(intAmount) + intAmountPassed;
|
||||
if (!totalAmount.isValid()) {
|
||||
break;
|
||||
}
|
||||
auto totalAmount = mozilla::CheckedInt64(durationAmount) + amountPassed;
|
||||
MOZ_ASSERT(totalAmount.isValid());
|
||||
|
||||
auto amountNanos = denominator * totalAmount;
|
||||
if (!amountNanos.isValid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto totalNanoseconds = dayLength * totalDays;
|
||||
totalNanoseconds += timeAndDays.time;
|
||||
totalNanoseconds += amountNanos;
|
||||
if (!totalNanoseconds.isValid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
double rounded;
|
||||
double total = 0;
|
||||
if (computeRemainder == ComputeRemainder::No) {
|
||||
if (!temporal::RoundNumberToIncrement(cx, totalNanoseconds.value(),
|
||||
denominator.value(), increment,
|
||||
roundingMode, &rounded)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
TruncateNumber(totalNanoseconds.value(), denominator.value(), &rounded,
|
||||
&total);
|
||||
if (computeRemainder == ComputeRemainder::Yes) {
|
||||
return TruncateNumber(totalNanoseconds.value(), denominator.value());
|
||||
}
|
||||
|
||||
*result = {rounded, total};
|
||||
return true;
|
||||
auto rounded = RoundNumberToIncrement(
|
||||
totalNanoseconds.value(), denominator.value(), increment, roundingMode);
|
||||
constexpr double total = 0;
|
||||
return {rounded, total};
|
||||
} while (false);
|
||||
|
||||
return RoundNumberToIncrementSlow(
|
||||
cx, durationAmount, amountPassed, durationDays, daysToAdd, timeAndDays,
|
||||
oneUnitDays, increment, roundingMode, computeRemainder, result);
|
||||
// Use int128 when values are too large for int64. Additionally assert all
|
||||
// values fit into int128.
|
||||
|
||||
// `dayLength` < 2**53
|
||||
auto dayLength = Int128{timeAndDays.dayLength};
|
||||
MOZ_ASSERT(dayLength < Int128{1} << 53);
|
||||
|
||||
// `abs(oneUnitDays)` < 200'000'000, log2(200'000'000) = ~27.57.
|
||||
auto denominator = dayLength * Int128{std::abs(oneUnitDays)};
|
||||
MOZ_ASSERT(denominator < Int128{1} << (53 + 28));
|
||||
|
||||
// log2(24*60*60) = ~16.4 and log2(2 * 200'000'000) = ~28.57.
|
||||
//
|
||||
// `abs(maxDurationDays)` ≤ 2**(53 - 16).
|
||||
// `abs(timeAndDays.days)` ≤ 2**(53 - 16).
|
||||
// `abs(daysToAdd)` ≤ 2**29.
|
||||
//
|
||||
// 2**(53 - 16) + 2**(53 - 16) + 2**29
|
||||
// = 2**37 + 2**37 + 2**29
|
||||
// = 2**38 + 2**29
|
||||
// ≤ 2**39
|
||||
auto totalDays = Int128{durationDays};
|
||||
totalDays += Int128{timeAndDays.days};
|
||||
totalDays += Int128{daysToAdd};
|
||||
MOZ_ASSERT(totalDays.abs() <= Uint128{1} << 39);
|
||||
|
||||
// `abs(durationAmount)` ≤ 2**32
|
||||
// `abs(amountPassed)` ≤ 2**32
|
||||
auto totalAmount = Int128{durationAmount} + Int128{amountPassed};
|
||||
MOZ_ASSERT(totalAmount.abs() <= Uint128{1} << 33);
|
||||
|
||||
// `denominator` < 2**(53 + 28)
|
||||
// `abs(totalAmount)` <= 2**33
|
||||
//
|
||||
// `denominator * totalAmount`
|
||||
// ≤ 2**(53 + 28) * 2**33
|
||||
// = 2**(53 + 28 + 33)
|
||||
// = 2**114
|
||||
auto amountNanos = denominator * totalAmount;
|
||||
MOZ_ASSERT(amountNanos.abs() <= Uint128{1} << 114);
|
||||
|
||||
// `dayLength` < 2**53
|
||||
// `totalDays` ≤ 2**39
|
||||
// `timeAndDays.time` < `dayLength` < 2**53
|
||||
// `amountNanos` ≤ 2**114
|
||||
//
|
||||
// `dayLength * totalDays`
|
||||
// ≤ 2**(53 + 39) = 2**92
|
||||
//
|
||||
// `dayLength * totalDays + timeAndDays.time`
|
||||
// ≤ 2**93
|
||||
//
|
||||
// `dayLength * totalDays + timeAndDays.time + amountNanos`
|
||||
// ≤ 2**115
|
||||
auto totalNanoseconds = dayLength * totalDays;
|
||||
totalNanoseconds += Int128{timeAndDays.time};
|
||||
totalNanoseconds += amountNanos;
|
||||
MOZ_ASSERT(totalNanoseconds.abs() <= Uint128{1} << 115);
|
||||
|
||||
if (computeRemainder == ComputeRemainder::Yes) {
|
||||
return TruncateNumber(totalNanoseconds, denominator);
|
||||
}
|
||||
|
||||
auto rounded = RoundNumberToIncrement(totalNanoseconds, denominator,
|
||||
increment, roundingMode);
|
||||
constexpr double total = 0;
|
||||
return {rounded, total};
|
||||
}
|
||||
|
||||
static bool RoundNumberToIncrement(JSContext* cx, double durationDays,
|
||||
const NormalizedTimeAndDays& timeAndDays,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode,
|
||||
ComputeRemainder computeRemainder,
|
||||
RoundedNumber* result) {
|
||||
constexpr double daysAmount = 0;
|
||||
constexpr double daysPassed = 0;
|
||||
static RoundedNumber RoundNumberToIncrement(
|
||||
double durationDays, const NormalizedTimeAndDays& timeAndDays,
|
||||
Increment increment, TemporalRoundingMode roundingMode,
|
||||
ComputeRemainder computeRemainder) {
|
||||
constexpr int64_t daysAmount = 0;
|
||||
constexpr int64_t daysPassed = 0;
|
||||
constexpr int32_t oneDayDays = 1;
|
||||
constexpr int32_t daysToAdd = 0;
|
||||
|
||||
return RoundNumberToIncrement(cx, daysAmount, daysPassed, durationDays,
|
||||
daysToAdd, timeAndDays, oneDayDays, increment,
|
||||
roundingMode, computeRemainder, result);
|
||||
return RoundNumberToIncrement(daysAmount, daysPassed, durationDays, daysToAdd,
|
||||
timeAndDays, oneDayDays, increment,
|
||||
roundingMode, computeRemainder);
|
||||
}
|
||||
|
||||
static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
|
||||
|
@ -3587,13 +3491,13 @@ static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
|
|||
}
|
||||
|
||||
// Step 10.m.
|
||||
double yearsPassed = timePassed.years;
|
||||
int64_t yearsPassed = int64_t(timePassed.years);
|
||||
|
||||
// Step 10.n.
|
||||
// Our implementation keeps |years| and |yearsPassed| separate.
|
||||
|
||||
// Step 10.o.
|
||||
Duration yearsPassedDuration = {yearsPassed};
|
||||
Duration yearsPassedDuration = {double(yearsPassed)};
|
||||
|
||||
// Steps 10.p-r.
|
||||
int32_t daysPassed;
|
||||
|
@ -3631,13 +3535,9 @@ static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
|
|||
}
|
||||
|
||||
// Steps 10.y-aa.
|
||||
RoundedNumber rounded;
|
||||
if (!RoundNumberToIncrement(cx, years, yearsPassed, days, daysToAdd,
|
||||
timeAndDays, oneYearDays, increment, roundingMode,
|
||||
computeRemainder, &rounded)) {
|
||||
return false;
|
||||
}
|
||||
auto [numYears, total] = rounded;
|
||||
auto [numYears, total] = RoundNumberToIncrement(
|
||||
years, yearsPassed, days, daysToAdd, timeAndDays, oneYearDays, increment,
|
||||
roundingMode, computeRemainder);
|
||||
|
||||
// Step 10.ab.
|
||||
int64_t numMonths = 0;
|
||||
|
@ -3647,8 +3547,8 @@ static bool RoundDurationYear(JSContext* cx, const NormalizedDuration& duration,
|
|||
constexpr auto time = NormalizedTimeDuration{};
|
||||
|
||||
// Step 20.
|
||||
if (std::abs(numYears) >= double(int64_t(1) << 32)) {
|
||||
return ThrowInvalidDurationPart(cx, numYears, "years",
|
||||
if (numYears.abs() >= (Uint128{1} << 32)) {
|
||||
return ThrowInvalidDurationPart(cx, double(numYears), "years",
|
||||
JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
|
||||
}
|
||||
|
||||
|
@ -3734,13 +3634,13 @@ static bool RoundDurationMonth(JSContext* cx,
|
|||
}
|
||||
|
||||
// Step 11.m.
|
||||
double monthsPassed = timePassed.months;
|
||||
int64_t monthsPassed = int64_t(timePassed.months);
|
||||
|
||||
// Step 11.n.
|
||||
// Our implementation keeps |months| and |monthsPassed| separate.
|
||||
|
||||
// Step 11.o.
|
||||
Duration monthsPassedDuration = {0, monthsPassed};
|
||||
Duration monthsPassedDuration = {0, double(monthsPassed)};
|
||||
|
||||
// Steps 11.p-r.
|
||||
int32_t daysPassed;
|
||||
|
@ -3778,13 +3678,9 @@ static bool RoundDurationMonth(JSContext* cx,
|
|||
}
|
||||
|
||||
// Steps 11.y-aa.
|
||||
RoundedNumber rounded;
|
||||
if (!RoundNumberToIncrement(cx, months, monthsPassed, days, daysToAdd,
|
||||
timeAndDays, oneMonthDays, increment,
|
||||
roundingMode, computeRemainder, &rounded)) {
|
||||
return false;
|
||||
}
|
||||
auto [numMonths, total] = rounded;
|
||||
auto [numMonths, total] = RoundNumberToIncrement(
|
||||
months, monthsPassed, days, daysToAdd, timeAndDays, oneMonthDays,
|
||||
increment, roundingMode, computeRemainder);
|
||||
|
||||
// Step 11.ab.
|
||||
int64_t numWeeks = 0;
|
||||
|
@ -3793,8 +3689,8 @@ static bool RoundDurationMonth(JSContext* cx,
|
|||
constexpr auto time = NormalizedTimeDuration{};
|
||||
|
||||
// Step 21.
|
||||
if (std::abs(numMonths) >= double(int64_t(1) << 32)) {
|
||||
return ThrowInvalidDurationPart(cx, numMonths, "months",
|
||||
if (numMonths.abs() >= (Uint128{1} << 32)) {
|
||||
return ThrowInvalidDurationPart(cx, double(numMonths), "months",
|
||||
JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
|
||||
}
|
||||
|
||||
|
@ -3850,13 +3746,13 @@ static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
|
|||
}
|
||||
|
||||
// Step 12.f.
|
||||
double weeksPassed = timePassed.weeks;
|
||||
int64_t weeksPassed = int64_t(timePassed.weeks);
|
||||
|
||||
// Step 12.g.
|
||||
// Our implementation keeps |weeks| and |weeksPassed| separate.
|
||||
|
||||
// Step 12.h.
|
||||
Duration weeksPassedDuration = {0, 0, weeksPassed};
|
||||
Duration weeksPassedDuration = {0, 0, double(weeksPassed)};
|
||||
|
||||
// Steps 12.i-k.
|
||||
Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
|
||||
|
@ -3895,20 +3791,16 @@ static bool RoundDurationWeek(JSContext* cx, const NormalizedDuration& duration,
|
|||
}
|
||||
|
||||
// Steps 12.r-t.
|
||||
RoundedNumber rounded;
|
||||
if (!RoundNumberToIncrement(cx, weeks, weeksPassed, days, daysToAdd,
|
||||
timeAndDays, oneWeekDays, increment, roundingMode,
|
||||
computeRemainder, &rounded)) {
|
||||
return false;
|
||||
}
|
||||
auto [numWeeks, total] = rounded;
|
||||
auto [numWeeks, total] = RoundNumberToIncrement(
|
||||
weeks, weeksPassed, days, daysToAdd, timeAndDays, oneWeekDays, increment,
|
||||
roundingMode, computeRemainder);
|
||||
|
||||
// Step 12.u.
|
||||
constexpr auto time = NormalizedTimeDuration{};
|
||||
|
||||
// Step 20.
|
||||
if (std::abs(numWeeks) >= double(int64_t(1) << 32)) {
|
||||
return ThrowInvalidDurationPart(cx, numWeeks, "weeks",
|
||||
if (numWeeks.abs() >= (Uint128{1} << 32)) {
|
||||
return ThrowInvalidDurationPart(cx, double(numWeeks), "weeks",
|
||||
JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
|
||||
}
|
||||
|
||||
|
@ -3930,12 +3822,11 @@ static bool RoundDurationDay(JSContext* cx, const NormalizedDuration& duration,
|
|||
auto [years, months, weeks, days] = duration.date;
|
||||
|
||||
// Steps 13.a-b.
|
||||
RoundedNumber rounded;
|
||||
if (!RoundNumberToIncrement(cx, days, timeAndDays, increment, roundingMode,
|
||||
computeRemainder, &rounded)) {
|
||||
return false;
|
||||
}
|
||||
auto [numDays, total] = rounded;
|
||||
auto [numDays, total] = RoundNumberToIncrement(
|
||||
days, timeAndDays, increment, roundingMode, computeRemainder);
|
||||
|
||||
MOZ_ASSERT(Int128{INT64_MIN} <= numDays && numDays <= Int128{INT64_MAX},
|
||||
"rounded days fits in int64");
|
||||
|
||||
// Step 13.c.
|
||||
constexpr auto time = NormalizedTimeDuration{};
|
||||
|
|
|
@ -622,22 +622,27 @@ NormalizedTimeDuration js::temporal::DifferenceInstant(
|
|||
/**
|
||||
* RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
|
||||
*/
|
||||
static bool RoundNumberToIncrementAsIfPositive(
|
||||
JSContext* cx, const Instant& x, int64_t increment,
|
||||
TemporalRoundingMode roundingMode, Instant* result) {
|
||||
static Instant RoundNumberToIncrementAsIfPositive(
|
||||
const Instant& x, int64_t increment, TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(IsValidEpochInstant(x));
|
||||
MOZ_ASSERT(increment > 0);
|
||||
MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day));
|
||||
|
||||
// This operation is equivalent to adjusting the rounding mode through
|
||||
// |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
|
||||
return RoundNumberToIncrement(cx, x, increment,
|
||||
ToPositiveRoundingMode(roundingMode), result);
|
||||
auto rounded =
|
||||
RoundNumberToIncrement(x.toTotalNanoseconds(), Int128{increment},
|
||||
ToPositiveRoundingMode(roundingMode));
|
||||
return Instant::fromNanoseconds(rounded);
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundTemporalInstant ( ns, increment, unit, roundingMode )
|
||||
*/
|
||||
bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns,
|
||||
Increment increment, TemporalUnit unit,
|
||||
TemporalRoundingMode roundingMode,
|
||||
Instant* result) {
|
||||
Instant js::temporal::RoundTemporalInstant(const Instant& ns,
|
||||
Increment increment,
|
||||
TemporalUnit unit,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(IsValidEpochInstant(ns));
|
||||
MOZ_ASSERT(increment >= Increment::min());
|
||||
MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day));
|
||||
|
@ -651,7 +656,7 @@ bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns,
|
|||
|
||||
// Step 7.
|
||||
return RoundNumberToIncrementAsIfPositive(
|
||||
cx, ns, increment.value() * toNanoseconds, roundingMode, result);
|
||||
ns, increment.value() * toNanoseconds, roundingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1226,11 +1231,8 @@ static bool Instant_round(JSContext* cx, const CallArgs& args) {
|
|||
}
|
||||
|
||||
// Step 17.
|
||||
Instant roundedNs;
|
||||
if (!RoundTemporalInstant(cx, instant, roundingIncrement, smallestUnit,
|
||||
roundingMode, &roundedNs)) {
|
||||
return false;
|
||||
}
|
||||
auto roundedNs = RoundTemporalInstant(instant, roundingIncrement,
|
||||
smallestUnit, roundingMode);
|
||||
|
||||
// Step 18.
|
||||
auto* result = CreateTemporalInstant(cx, roundedNs);
|
||||
|
@ -1338,11 +1340,8 @@ static bool Instant_toString(JSContext* cx, const CallArgs& args) {
|
|||
}
|
||||
|
||||
// Step 12.
|
||||
Instant ns;
|
||||
if (!RoundTemporalInstant(cx, instant, precision.increment, precision.unit,
|
||||
roundingMode, &ns)) {
|
||||
return false;
|
||||
}
|
||||
auto ns = RoundTemporalInstant(instant, precision.increment, precision.unit,
|
||||
roundingMode);
|
||||
|
||||
// Step 13.
|
||||
Rooted<InstantObject*> roundedInstant(cx, CreateTemporalInstant(cx, ns));
|
||||
|
|
|
@ -127,9 +127,9 @@ Instant GetUTCEpochNanoseconds(const PlainDateTime& dateTime,
|
|||
/**
|
||||
* RoundTemporalInstant ( ns, increment, unit, roundingMode )
|
||||
*/
|
||||
bool RoundTemporalInstant(JSContext* cx, const Instant& ns, Increment increment,
|
||||
TemporalUnit unit, TemporalRoundingMode roundingMode,
|
||||
Instant* result);
|
||||
Instant RoundTemporalInstant(const Instant& ns, Increment increment,
|
||||
TemporalUnit unit,
|
||||
TemporalRoundingMode roundingMode);
|
||||
|
||||
/**
|
||||
* AddNormalizedTimeDurationToEpochNanoseconds ( d, epochNs )
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "builtin/temporal/PlainTime.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
|
@ -46,7 +45,6 @@
|
|||
#include "js/PropertySpec.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/Value.h"
|
||||
#include "vm/BigIntType.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/JSAtomState.h"
|
||||
|
@ -744,59 +742,6 @@ bool js::temporal::ToTemporalTimeRecord(JSContext* cx,
|
|||
return ::ToTemporalTimeRecord(cx, temporalTimeLike, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
static int64_t RoundNumberToIncrement(int64_t x, TemporalUnit unit,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(x >= 0);
|
||||
MOZ_ASSERT(x < ToNanoseconds(TemporalUnit::Day));
|
||||
|
||||
MOZ_ASSERT(unit >= TemporalUnit::Day);
|
||||
MOZ_ASSERT_IF(unit == TemporalUnit::Day, increment == Increment{1});
|
||||
MOZ_ASSERT_IF(unit > TemporalUnit::Day,
|
||||
increment <= MaximumTemporalDurationRoundingIncrement(unit));
|
||||
|
||||
int64_t divisor = ToNanoseconds(unit) * increment.value();
|
||||
MOZ_ASSERT(divisor > 0);
|
||||
MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
|
||||
|
||||
// Division by one has no remainder.
|
||||
if (divisor == 1) {
|
||||
MOZ_ASSERT(increment == Increment{1});
|
||||
return x;
|
||||
}
|
||||
|
||||
// Steps 1-8.
|
||||
int64_t rounded = Divide(x, divisor, roundingMode);
|
||||
|
||||
// Step 9.
|
||||
mozilla::CheckedInt64 result = rounded;
|
||||
result *= increment.value();
|
||||
|
||||
MOZ_ASSERT(result.isValid(), "can't overflow when inputs are all in range");
|
||||
|
||||
return result.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
static int64_t RoundNumberToIncrement(int64_t x, int64_t divisor,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(x >= 0);
|
||||
MOZ_ASSERT(x < ToNanoseconds(TemporalUnit::Day));
|
||||
MOZ_ASSERT(divisor > 0);
|
||||
MOZ_ASSERT(increment == Increment{1}, "Rounding increment for 'day' is 1");
|
||||
|
||||
// Steps 1-2. (Not applicable in our implementation)
|
||||
|
||||
// Steps 3-8.
|
||||
return Divide(x, divisor, roundingMode);
|
||||
}
|
||||
|
||||
static int64_t TimeToNanos(const PlainTime& time) {
|
||||
// No overflow possible because the input is a valid time.
|
||||
MOZ_ASSERT(IsValidTime(time));
|
||||
|
@ -887,11 +832,15 @@ RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
|
|||
}
|
||||
|
||||
// Step 9.
|
||||
int64_t r = ::RoundNumberToIncrement(TimeToNanos(quantity), unit, increment,
|
||||
roundingMode);
|
||||
MOZ_ASSERT(r == int64_t(int32_t(r)),
|
||||
"no overflow possible due to limited range of arguments");
|
||||
*result = r;
|
||||
int64_t nanos = TimeToNanos(quantity);
|
||||
MOZ_ASSERT(0 <= nanos && nanos < ToNanoseconds(TemporalUnit::Day));
|
||||
|
||||
auto r = RoundNumberToIncrement(nanos, ToNanoseconds(unit), increment,
|
||||
roundingMode);
|
||||
MOZ_ASSERT(r == Int128{int32_t(r)},
|
||||
"can't overflow when inputs are all in range");
|
||||
|
||||
*result = int32_t(r);
|
||||
|
||||
// Step 10.
|
||||
if (unit == TemporalUnit::Day) {
|
||||
|
@ -924,28 +873,27 @@ RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
|
|||
|
||||
// Step 2.
|
||||
int64_t quantity = TimeToNanos(time);
|
||||
MOZ_ASSERT(quantity < ToNanoseconds(TemporalUnit::Day));
|
||||
MOZ_ASSERT(0 <= quantity && quantity < ToNanoseconds(TemporalUnit::Day));
|
||||
|
||||
// Steps 3-8. (Not applicable)
|
||||
|
||||
// Step 9.
|
||||
int64_t divisor;
|
||||
if (auto checkedDiv = dayLengthNs.toNanoseconds(); checkedDiv.isValid()) {
|
||||
divisor = checkedDiv.value();
|
||||
} else {
|
||||
// When the divisor is too large, the expression `quantity / divisor` is a
|
||||
// value near zero. Substitute |divisor| with an equivalent expression.
|
||||
// Choose |86'400'000'000'000| which will give a similar result because
|
||||
// |quantity| is guaranteed to be lower than |86'400'000'000'000|.
|
||||
divisor = ToNanoseconds(TemporalUnit::Day);
|
||||
}
|
||||
//
|
||||
// When the divisor is too large, the expression `quantity / divisor` is a
|
||||
// value near zero. Substitute |divisor| with an equivalent expression.
|
||||
// Choose |86'400'000'000'000| which will give a similar result because
|
||||
// |quantity| is guaranteed to be lower than |86'400'000'000'000|.
|
||||
int64_t divisor = int64_t(std::min(dayLengthNs.toTotalNanoseconds(),
|
||||
Int128{ToNanoseconds(TemporalUnit::Day)}));
|
||||
MOZ_ASSERT(divisor > 0);
|
||||
MOZ_ASSERT(increment == Increment{1}, "Rounding increment for 'day' is 1");
|
||||
|
||||
int64_t result =
|
||||
::RoundNumberToIncrement(quantity, divisor, increment, roundingMode);
|
||||
auto result =
|
||||
RoundNumberToIncrement(quantity, divisor, increment, roundingMode);
|
||||
MOZ_ASSERT(result == Int128{int64_t(result)});
|
||||
|
||||
// Step 10.
|
||||
return {result, {0, 0, 0, 0, 0, 0}};
|
||||
return {int64_t(result), {0, 0, 0, 0, 0, 0}};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
#include "js/RootingAPI.h"
|
||||
#include "js/String.h"
|
||||
#include "js/Value.h"
|
||||
#include "vm/BigIntType.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/JSAtomState.h"
|
||||
|
@ -440,558 +439,6 @@ bool js::temporal::ToTemporalRoundingMode(JSContext* cx,
|
|||
return true;
|
||||
}
|
||||
|
||||
static BigInt* Divide(JSContext* cx, Handle<BigInt*> dividend, int64_t divisor,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(divisor > 0);
|
||||
|
||||
Rooted<BigInt*> div(cx, BigInt::createFromInt64(cx, divisor));
|
||||
if (!div) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> quotient(cx);
|
||||
Rooted<BigInt*> remainder(cx);
|
||||
if (!BigInt::divmod(cx, dividend, div, "ient, &remainder)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// No rounding needed when the remainder is zero.
|
||||
if (remainder->isZero()) {
|
||||
return quotient;
|
||||
}
|
||||
|
||||
switch (roundingMode) {
|
||||
case TemporalRoundingMode::Ceil: {
|
||||
if (!remainder->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::Floor: {
|
||||
if (remainder->isNegative()) {
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::Trunc:
|
||||
// BigInt division truncates.
|
||||
return quotient;
|
||||
case TemporalRoundingMode::Expand: {
|
||||
if (!remainder->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
case TemporalRoundingMode::HalfCeil: {
|
||||
int64_t rem;
|
||||
MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
|
||||
|
||||
if (!remainder->isNegative()) {
|
||||
if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
} else {
|
||||
if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfFloor: {
|
||||
int64_t rem;
|
||||
MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
|
||||
|
||||
if (remainder->isNegative()) {
|
||||
if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
} else {
|
||||
if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfExpand: {
|
||||
int64_t rem;
|
||||
MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
|
||||
|
||||
if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfTrunc: {
|
||||
int64_t rem;
|
||||
MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
|
||||
|
||||
if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfEven: {
|
||||
int64_t rem;
|
||||
MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
|
||||
|
||||
if (uint64_t(std::abs(rem)) * 2 == uint64_t(divisor)) {
|
||||
bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1;
|
||||
if (isOdd) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
}
|
||||
if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CRASH("invalid rounding mode");
|
||||
}
|
||||
|
||||
static BigInt* Divide(JSContext* cx, Handle<BigInt*> dividend,
|
||||
Handle<BigInt*> divisor,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(!divisor->isNegative());
|
||||
MOZ_ASSERT(!divisor->isZero());
|
||||
|
||||
Rooted<BigInt*> quotient(cx);
|
||||
Rooted<BigInt*> remainder(cx);
|
||||
if (!BigInt::divmod(cx, dividend, divisor, "ient, &remainder)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// No rounding needed when the remainder is zero.
|
||||
if (remainder->isZero()) {
|
||||
return quotient;
|
||||
}
|
||||
|
||||
switch (roundingMode) {
|
||||
case TemporalRoundingMode::Ceil: {
|
||||
if (!remainder->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::Floor: {
|
||||
if (remainder->isNegative()) {
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::Trunc:
|
||||
// BigInt division truncates.
|
||||
return quotient;
|
||||
case TemporalRoundingMode::Expand: {
|
||||
if (!remainder->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
case TemporalRoundingMode::HalfCeil: {
|
||||
BigInt* rem = BigInt::add(cx, remainder, remainder);
|
||||
if (!rem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!remainder->isNegative()) {
|
||||
if (BigInt::absoluteCompare(rem, divisor) >= 0) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
} else {
|
||||
if (BigInt::absoluteCompare(rem, divisor) > 0) {
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfFloor: {
|
||||
BigInt* rem = BigInt::add(cx, remainder, remainder);
|
||||
if (!rem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (remainder->isNegative()) {
|
||||
if (BigInt::absoluteCompare(rem, divisor) >= 0) {
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
} else {
|
||||
if (BigInt::absoluteCompare(rem, divisor) > 0) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfExpand: {
|
||||
BigInt* rem = BigInt::add(cx, remainder, remainder);
|
||||
if (!rem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (BigInt::absoluteCompare(rem, divisor) >= 0) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfTrunc: {
|
||||
BigInt* rem = BigInt::add(cx, remainder, remainder);
|
||||
if (!rem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (BigInt::absoluteCompare(rem, divisor) > 0) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
case TemporalRoundingMode::HalfEven: {
|
||||
BigInt* rem = BigInt::add(cx, remainder, remainder);
|
||||
if (!rem) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (BigInt::absoluteCompare(rem, divisor) == 0) {
|
||||
bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1;
|
||||
if (isOdd) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
}
|
||||
if (BigInt::absoluteCompare(rem, divisor) > 0) {
|
||||
if (!dividend->isNegative()) {
|
||||
return BigInt::inc(cx, quotient);
|
||||
}
|
||||
return BigInt::dec(cx, quotient);
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CRASH("invalid rounding mode");
|
||||
}
|
||||
|
||||
static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle<BigInt*> x,
|
||||
int64_t divisor, int64_t increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
// Steps 1-8.
|
||||
Rooted<BigInt*> rounded(cx, Divide(cx, x, divisor, roundingMode));
|
||||
if (!rounded) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We can skip the next step when |increment=1|.
|
||||
if (increment == 1) {
|
||||
return rounded;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
Rooted<BigInt*> inc(cx, BigInt::createFromInt64(cx, increment));
|
||||
if (!inc) {
|
||||
return nullptr;
|
||||
}
|
||||
return BigInt::mul(cx, rounded, inc);
|
||||
}
|
||||
|
||||
static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle<BigInt*> x,
|
||||
int64_t increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
return RoundNumberToIncrementSlow(cx, x, increment, increment, roundingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool js::temporal::RoundNumberToIncrement(JSContext* cx, const Instant& x,
|
||||
int64_t increment,
|
||||
TemporalRoundingMode roundingMode,
|
||||
Instant* result) {
|
||||
MOZ_ASSERT(IsValidEpochInstant(x));
|
||||
MOZ_ASSERT(increment > 0);
|
||||
MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day));
|
||||
|
||||
// Fast path for the default case.
|
||||
if (increment == 1) {
|
||||
*result = x;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dividing zero is always zero.
|
||||
if (x == Instant{}) {
|
||||
*result = x;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fast-path when we can perform the whole computation with int64 values.
|
||||
if (auto num = x.toNanoseconds(); MOZ_LIKELY(num.isValid())) {
|
||||
// Steps 1-8.
|
||||
int64_t rounded = Divide(num.value(), increment, roundingMode);
|
||||
|
||||
// Step 9.
|
||||
mozilla::CheckedInt64 checked = rounded;
|
||||
checked *= increment;
|
||||
if (MOZ_LIKELY(checked.isValid())) {
|
||||
*result = Instant::fromNanoseconds(checked.value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Rooted<BigInt*> bi(cx, ToEpochNanoseconds(cx, x));
|
||||
if (!bi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* rounded = RoundNumberToIncrementSlow(cx, bi, increment, roundingMode);
|
||||
if (!rounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = ToInstant(rounded);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator,
|
||||
TemporalUnit unit,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode,
|
||||
double* result) {
|
||||
MOZ_ASSERT(unit >= TemporalUnit::Day);
|
||||
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
|
||||
|
||||
// Take the slow path when the increment is too large.
|
||||
if (MOZ_UNLIKELY(increment > Increment{100'000})) {
|
||||
Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
|
||||
if (!bi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> denominator(
|
||||
cx, BigInt::createFromInt64(cx, ToNanoseconds(unit)));
|
||||
if (!denominator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return RoundNumberToIncrement(cx, bi, denominator, increment, roundingMode,
|
||||
result);
|
||||
}
|
||||
|
||||
int64_t divisor = ToNanoseconds(unit) * increment.value();
|
||||
MOZ_ASSERT(divisor > 0);
|
||||
MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000);
|
||||
|
||||
// Division by one has no remainder.
|
||||
if (divisor == 1) {
|
||||
MOZ_ASSERT(increment == Increment{1});
|
||||
*result = double(numerator);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Steps 1-8.
|
||||
int64_t rounded = Divide(numerator, divisor, roundingMode);
|
||||
|
||||
// Step 9.
|
||||
mozilla::CheckedInt64 checked = rounded;
|
||||
checked *= increment.value();
|
||||
if (checked.isValid()) {
|
||||
*result = double(checked.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
|
||||
if (!bi) {
|
||||
return false;
|
||||
}
|
||||
return RoundNumberToIncrement(cx, bi, unit, increment, roundingMode, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool js::temporal::RoundNumberToIncrement(
|
||||
JSContext* cx, Handle<BigInt*> numerator, TemporalUnit unit,
|
||||
Increment increment, TemporalRoundingMode roundingMode, double* result) {
|
||||
MOZ_ASSERT(unit >= TemporalUnit::Day);
|
||||
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
|
||||
|
||||
// Take the slow path when the increment is too large.
|
||||
if (MOZ_UNLIKELY(increment > Increment{100'000})) {
|
||||
Rooted<BigInt*> denominator(
|
||||
cx, BigInt::createFromInt64(cx, ToNanoseconds(unit)));
|
||||
if (!denominator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return RoundNumberToIncrement(cx, numerator, denominator, increment,
|
||||
roundingMode, result);
|
||||
}
|
||||
|
||||
int64_t divisor = ToNanoseconds(unit) * increment.value();
|
||||
MOZ_ASSERT(divisor > 0);
|
||||
MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000);
|
||||
|
||||
// Division by one has no remainder.
|
||||
if (divisor == 1) {
|
||||
MOZ_ASSERT(increment == Increment{1});
|
||||
*result = BigInt::numberValue(numerator);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dividing zero is always zero.
|
||||
if (numerator->isZero()) {
|
||||
*result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// All callers are already in the slow path, so we don't need to fast-path the
|
||||
// case when |x| can be represented by an int64 value.
|
||||
|
||||
// Steps 1-9.
|
||||
auto* rounded = RoundNumberToIncrementSlow(cx, numerator, divisor,
|
||||
increment.value(), roundingMode);
|
||||
if (!rounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = BigInt::numberValue(rounded);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator,
|
||||
int64_t denominator,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode,
|
||||
double* result) {
|
||||
MOZ_ASSERT(denominator > 0);
|
||||
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
|
||||
|
||||
// Dividing zero is always zero.
|
||||
if (numerator == 0) {
|
||||
*result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We don't have to adjust the divisor when |increment=1|.
|
||||
if (increment == Increment{1}) {
|
||||
int64_t divisor = denominator;
|
||||
int64_t rounded = Divide(numerator, divisor, roundingMode);
|
||||
|
||||
*result = double(rounded);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto divisor = mozilla::CheckedInt64(denominator) * increment.value();
|
||||
if (MOZ_LIKELY(divisor.isValid())) {
|
||||
MOZ_ASSERT(divisor.value() > 0);
|
||||
|
||||
// Steps 1-8.
|
||||
int64_t rounded = Divide(numerator, divisor.value(), roundingMode);
|
||||
|
||||
// Step 9.
|
||||
auto adjusted = mozilla::CheckedInt64(rounded) * increment.value();
|
||||
if (MOZ_LIKELY(adjusted.isValid())) {
|
||||
*result = double(adjusted.value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path on overflow.
|
||||
|
||||
Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
|
||||
if (!bi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> denom(cx, BigInt::createFromInt64(cx, denominator));
|
||||
if (!denom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return RoundNumberToIncrement(cx, bi, denom, increment, roundingMode, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool js::temporal::RoundNumberToIncrement(
|
||||
JSContext* cx, Handle<BigInt*> numerator, Handle<BigInt*> denominator,
|
||||
Increment increment, TemporalRoundingMode roundingMode, double* result) {
|
||||
MOZ_ASSERT(!denominator->isNegative());
|
||||
MOZ_ASSERT(!denominator->isZero());
|
||||
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
|
||||
|
||||
// Dividing zero is always zero.
|
||||
if (numerator->isZero()) {
|
||||
*result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We don't have to adjust the divisor when |increment=1|.
|
||||
if (increment == Increment{1}) {
|
||||
auto divisor = denominator;
|
||||
|
||||
auto* rounded = Divide(cx, numerator, divisor, roundingMode);
|
||||
if (!rounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = BigInt::numberValue(rounded);
|
||||
return true;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> inc(cx, BigInt::createFromUint64(cx, increment.value()));
|
||||
if (!inc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<BigInt*> divisor(cx, BigInt::mul(cx, denominator, inc));
|
||||
if (!divisor) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(!divisor->isNegative());
|
||||
MOZ_ASSERT(!divisor->isZero());
|
||||
|
||||
// Steps 1-8.
|
||||
Rooted<BigInt*> rounded(cx, Divide(cx, numerator, divisor, roundingMode));
|
||||
if (!rounded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
auto* adjusted = BigInt::mul(cx, rounded, inc);
|
||||
if (!adjusted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = BigInt::numberValue(adjusted);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// Copied from mozilla::CheckedInt.
|
||||
static bool IsValidMul(const Int128& x, const Int128& y) {
|
||||
|
@ -1008,14 +455,81 @@ static bool IsValidMul(const Int128& x, const Int128& y) {
|
|||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
Int128 js::temporal::RoundNumberToIncrement(int64_t numerator,
|
||||
int64_t denominator,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(denominator > 0);
|
||||
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
|
||||
|
||||
// Dividing zero is always zero.
|
||||
if (numerator == 0) {
|
||||
return Int128{0};
|
||||
}
|
||||
|
||||
// We don't have to adjust the divisor when |increment=1|.
|
||||
if (increment == Increment{1}) {
|
||||
// Steps 1-8 and implicit step 9.
|
||||
return Int128{Divide(numerator, denominator, roundingMode)};
|
||||
}
|
||||
|
||||
// Fast-path when we can perform the whole computation with int64 values.
|
||||
auto divisor = mozilla::CheckedInt64(denominator) * increment.value();
|
||||
if (MOZ_LIKELY(divisor.isValid())) {
|
||||
MOZ_ASSERT(divisor.value() > 0);
|
||||
|
||||
// Steps 1-8.
|
||||
int64_t rounded = Divide(numerator, divisor.value(), roundingMode);
|
||||
|
||||
// Step 9.
|
||||
auto result = mozilla::CheckedInt64(rounded) * increment.value();
|
||||
if (MOZ_LIKELY(result.isValid())) {
|
||||
return Int128{result.value()};
|
||||
}
|
||||
}
|
||||
|
||||
// Int128 path on overflow.
|
||||
return RoundNumberToIncrement(Int128{numerator}, Int128{denominator},
|
||||
increment, roundingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
Int128 js::temporal::RoundNumberToIncrement(const Int128& numerator,
|
||||
const Int128& denominator,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(denominator > Int128{0});
|
||||
MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
|
||||
|
||||
auto inc = Int128{increment.value()};
|
||||
MOZ_ASSERT(IsValidMul(denominator, inc), "unsupported overflow");
|
||||
|
||||
auto divisor = denominator * inc;
|
||||
MOZ_ASSERT(divisor > Int128{0});
|
||||
|
||||
// Steps 1-8.
|
||||
auto rounded = Divide(numerator, divisor, roundingMode);
|
||||
|
||||
// Step 9.
|
||||
MOZ_ASSERT(IsValidMul(rounded, inc), "unsupported overflow");
|
||||
return rounded * inc;
|
||||
}
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
Int128 js::temporal::RoundNumberToIncrement(const Int128& x,
|
||||
const Int128& increment,
|
||||
TemporalRoundingMode roundingMode) {
|
||||
MOZ_ASSERT(increment > Int128{0});
|
||||
|
||||
// Steps 1-8.
|
||||
auto rounded = Divide(numerator, increment, roundingMode);
|
||||
auto rounded = Divide(x, increment, roundingMode);
|
||||
|
||||
// Step 9.
|
||||
MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow");
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "jstypes.h"
|
||||
|
||||
#include "builtin/temporal/Int128.h"
|
||||
#include "builtin/temporal/TemporalRoundingMode.h"
|
||||
#include "builtin/temporal/TemporalUnit.h"
|
||||
#include "js/RootingAPI.h"
|
||||
|
@ -35,10 +36,6 @@ class TemporalObject : public NativeObject {
|
|||
static const ClassSpec classSpec_;
|
||||
};
|
||||
|
||||
struct Instant;
|
||||
struct PlainTime;
|
||||
class Int128;
|
||||
|
||||
/**
|
||||
* Rounding increment, which is an integer in the range [1, 1'000'000'000].
|
||||
*
|
||||
|
@ -172,43 +169,22 @@ bool ToTemporalRoundingMode(JSContext* cx, JS::Handle<JSObject*> options,
|
|||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
Int128 RoundNumberToIncrement(const Int128& x, const Int128& increment,
|
||||
Int128 RoundNumberToIncrement(int64_t numerator, int64_t denominator,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool RoundNumberToIncrement(JSContext* cx, const Instant& x, int64_t increment,
|
||||
TemporalRoundingMode roundingMode, Instant* result);
|
||||
Int128 RoundNumberToIncrement(const Int128& numerator,
|
||||
const Int128& denominator, Increment increment,
|
||||
TemporalRoundingMode roundingMode);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool RoundNumberToIncrement(JSContext* cx, int64_t numerator, TemporalUnit unit,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode, double* result);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool RoundNumberToIncrement(JSContext* cx, JS::Handle<JS::BigInt*> numerator,
|
||||
TemporalUnit unit, Increment increment,
|
||||
TemporalRoundingMode roundingMode, double* result);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool RoundNumberToIncrement(JSContext* cx, int64_t numerator,
|
||||
int64_t denominator, Increment increment,
|
||||
TemporalRoundingMode roundingMode, double* result);
|
||||
|
||||
/**
|
||||
* RoundNumberToIncrement ( x, increment, roundingMode )
|
||||
*/
|
||||
bool RoundNumberToIncrement(JSContext* cx, JS::Handle<JS::BigInt*> numerator,
|
||||
JS::Handle<JS::BigInt*> denominator,
|
||||
Increment increment,
|
||||
TemporalRoundingMode roundingMode, double* result);
|
||||
Int128 RoundNumberToIncrement(const Int128& x, const Int128& increment,
|
||||
TemporalRoundingMode roundingMode);
|
||||
|
||||
enum class CalendarOption { Auto, Always, Never, Critical };
|
||||
|
||||
|
|
|
@ -636,11 +636,8 @@ JSString* js::temporal::TemporalZonedDateTimeToString(
|
|||
// Steps 1-3. (Not applicable in our implementation.)
|
||||
|
||||
// Step 4.
|
||||
Instant ns;
|
||||
if (!RoundTemporalInstant(cx, zonedDateTime.instant(), increment, unit,
|
||||
roundingMode, &ns)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ns = RoundTemporalInstant(zonedDateTime.instant(), increment, unit,
|
||||
roundingMode);
|
||||
|
||||
// Step 5.
|
||||
auto timeZone = zonedDateTime.timeZone();
|
||||
|
|
Загрузка…
Ссылка в новой задаче