///|
/// Rounds a value to a multiple of an increment.
///
/// The value is carried as an exact `dividend / divisor` pair so that every
/// comparison against the midpoint stays in integer space; Temporal's
/// nanosecond quantities are far too large to round through a `Double` without
/// losing the tie cases.
///
/// Spec:
priv struct IncrementRounder {
/// Whether the value being rounded is non-negative.
is_positive : Bool
dividend : @int128.Int128
divisor : @int128.Int128
}
///|
/// Prepares `number` to be rounded to a multiple of `increment`.
fn IncrementRounder::from_signed_num(
number : @int128.Int128,
increment : @int128.Int128,
) -> IncrementRounder raise TemporalError {
if increment.signum() <= 0 {
raise AssertError("rounding increment must be positive")
}
{ is_positive: !number.is_negative(), dividend: number, divisor: increment }
}
///|
/// `RoundNumberToIncrement`: rounds the value, honouring its sign.
fn IncrementRounder::round(
self : IncrementRounder,
mode : RoundingMode,
) -> @int128.Int128 {
let unsigned_mode = mode.to_unsigned(self.is_positive)
// Work on the magnitude, then restore the sign, so that the "half away from
// zero" family behaves symmetrically about zero.
let dividend = if self.is_positive {
self.dividend
} else {
self.dividend.neg()
}
let rounded = apply_unsigned_rounding_mode(
dividend,
self.divisor,
unsigned_mode,
)
let rounded = if self.is_positive { rounded } else { rounded.neg() }
rounded.mul(self.divisor)
}
///|
/// `RoundNumberToIncrementAsIfPositive`: rounds as though the value were
/// positive, which is what epoch-nanosecond rounding needs so that instants
/// before the epoch round the same direction as those after it.
fn IncrementRounder::round_as_if_positive(
self : IncrementRounder,
mode : RoundingMode,
) -> @int128.Int128 {
let unsigned_mode = mode.to_unsigned(true)
let rounded = apply_unsigned_rounding_mode(
self.dividend,
self.divisor,
unsigned_mode,
)
rounded.mul(self.divisor)
}
///|
/// `ApplyUnsignedRoundingMode`, returning the rounded quotient.
///
/// Spec:
fn apply_unsigned_rounding_mode(
dividend : @int128.Int128,
divisor : @int128.Int128,
mode : UnsignedRoundingMode,
) -> @int128.Int128 {
let (floor, remainder) = dividend.div_rem_euclid(divisor)
// The quotient is exact, so every mode agrees on it.
if remainder.is_zero() {
return floor
}
let ceil = floor.add(@int128.one)
match mode {
Zero => floor
Infinity => ceil
_ => {
let two = @int128.of_int(2)
let midway = divisor.div_euclid(two)
// With an odd divisor the true midpoint falls between two integers, so
// the remainder can never actually tie.
let cmp = if remainder == midway && !divisor.rem_euclid(two).is_zero() {
-1
} else {
remainder.compare(midway)
}
if cmp < 0 {
floor
} else if cmp > 0 {
ceil
} else {
match mode {
HalfZero => floor
HalfInfinity => ceil
// half-even: keep whichever of the two neighbours is even.
_ => if floor.rem_euclid(two).is_zero() { floor } else { ceil }
}
}
}
}
}
///|
/// `ApplyUnsignedRoundingMode` for a value expressed as `dividend / divisor`
/// between two already-known neighbours `r1` and `r2`.
///
/// The nudge-rounding code knows the surrounding calendar-unit values but not
/// the quotient itself, so it needs this variant rather than the one above.
fn UnsignedRoundingMode::apply(
self : UnsignedRoundingMode,
dividend : @int128.Int128,
divisor : @int128.Int128,
r1 : @int128.Int128,
r2 : @int128.Int128,
) -> @int128.Int128 {
// Multiply through by `divisor` to keep every comparison in integer space.
if dividend == r1.mul(divisor) {
return r1
}
if self is Zero {
return r1
}
if self is Infinity {
return r2
}
let d1 = dividend.sub(r1.mul(divisor))
let d2 = r2.mul(divisor).sub(dividend)
let cmp = d1.compare(d2)
if cmp < 0 {
r1
} else if cmp > 0 {
r2
} else {
match self {
HalfZero => r1
HalfInfinity => r2
_ => {
// half-even, over neighbours that are `r2 - r1` apart.
let two = @int128.of_int(2)
let diff = r2.sub(r1)
if r1.div_euclid(diff).rem_euclid(two).is_zero() {
r1
} else {
r2
}
}
}
}
}