// The exact rounding primitives every other operation is built on.
//
// Money is stored as an integer number of minor units, so rounding only ever
// happens when an operation is not closed over the integers: a division, a
// percentage, a tax rate or a currency conversion. Those go through
// `big_div_round`, which forms the intermediate in arbitrary precision and
// reports an overflow instead of wrapping.
///|
/// How a value that falls between two minor units is resolved.
///
/// The names are the ones accounting and payment code uses. `HalfUp` is the
/// common commercial default, `HalfEven` is banker's rounding, `Ceiling` and
/// `Floor` are towards positive and negative infinity.
pub(all) enum RoundingMode {
HalfUp
HalfDown
HalfEven
Up
Down
Ceiling
Floor
} derive(Eq, Debug)
///|
pub extend RoundingMode with Eq::{not_equal, equal}
///|
pub extend RoundingMode with @debug.Debug::{to_repr}
///|
pub fn RoundingMode::label(self : RoundingMode) -> String {
match self {
HalfUp => "half-up"
HalfDown => "half-down"
HalfEven => "half-even"
Up => "up"
Down => "down"
Ceiling => "ceiling"
Floor => "floor"
}
}
///|
let big_two : @bigint.BigInt = @bigint.BigInt::from_int(2)
///|
let big_one : @bigint.BigInt = @bigint.BigInt::from_int(1)
///|
/// Narrows a value that is known to be exact, reporting an overflow rather than
/// truncating when it does not fit.
fn big_to_int64(
value : @bigint.BigInt,
operation : String,
) -> Int64 raise MoneyError {
if value.compare_int64(@int64.MAX_VALUE) > 0 ||
value.compare_int64(@int64.MIN_VALUE) < 0 {
raise Overflow(operation)
}
value.to_int64()
}
///|
/// Rounds `numerator / denominator` to the nearest integer under `mode`.
///
/// The quotient is computed in arbitrary precision, so large amounts, large
/// rates and large divisors are all handled without an intermediate overflow.
fn big_div_round(
numerator : @bigint.BigInt,
denominator : @bigint.BigInt,
mode : RoundingMode,
) -> Int64 raise MoneyError {
if denominator.is_zero() {
raise DivisionByZero
}
let quotient = numerator.div(denominator)
let remainder = numerator.mod(denominator)
if remainder.is_zero() {
return big_to_int64(quotient, "division")
}
let negative_remainder = remainder.compare_int(0) < 0
let abs_remainder = if negative_remainder {
remainder.neg()
} else {
remainder
}
let negative_denominator = denominator.compare_int(0) < 0
let abs_denominator = if negative_denominator {
denominator.neg()
} else {
denominator
}
let twice_remainder = abs_remainder.mul(big_two)
let comparison = twice_remainder.compare(abs_denominator)
let quotient_even = quotient.mod(big_two).is_zero()
let away_from_zero = match mode {
HalfUp => comparison >= 0
HalfDown => comparison > 0
HalfEven => comparison > 0 || (comparison == 0 && !quotient_even)
Up => true
Down => false
Ceiling => !negative_remainder
Floor => negative_remainder
}
if !away_from_zero {
return big_to_int64(quotient, "division")
}
let bump = if negative_remainder { big_one.neg() } else { big_one }
big_to_int64(quotient.add(bump), "division")
}
///|
/// Rounds `numerator / denominator` to an integer.
pub fn div_round(
numerator : Int64,
denominator : Int64,
mode : RoundingMode,
) -> Int64 raise MoneyError {
big_div_round(
@bigint.BigInt::from_int64(numerator),
@bigint.BigInt::from_int64(denominator),
mode,
)
}
///|
/// Computes `left * right / divisor` exactly and rounds once, at the end.
///
/// This is the shape of every rate application in the library: apply a
/// percentage, add tax, convert a currency. Forming the product in arbitrary
/// precision means an intermediate that does not fit 64 bits cannot silently
/// wrap, and rounding once at the end avoids the double-rounding error of
/// rounding the product before dividing.
pub fn mul_div_round(
left : Int64,
right : Int64,
divisor : Int64,
mode : RoundingMode,
) -> Int64 raise MoneyError {
big_div_round(
@bigint.BigInt::from_int64(left).mul(@bigint.BigInt::from_int64(right)),
@bigint.BigInt::from_int64(divisor),
mode,
)
}
///|
/// Checked addition; `operation` names the caller in the error.
pub fn add_checked(
left : Int64,
right : Int64,
operation : String,
) -> Int64 raise MoneyError {
if right > 0L && left > @int64.MAX_VALUE - right {
raise Overflow(operation)
}
if right < 0L && left < @int64.MIN_VALUE - right {
raise Overflow(operation)
}
left + right
}
///|
/// Checked subtraction.
pub fn sub_checked(
left : Int64,
right : Int64,
operation : String,
) -> Int64 raise MoneyError {
if right > 0L && left < @int64.MIN_VALUE + right {
raise Overflow(operation)
}
if right < 0L && left > @int64.MAX_VALUE + right {
raise Overflow(operation)
}
left - right
}
///|
/// Checked multiplication.
pub fn mul_checked(
left : Int64,
right : Int64,
operation : String,
) -> Int64 raise MoneyError {
if left == 0L || right == 0L {
return 0L
}
big_to_int64(
@bigint.BigInt::from_int64(left).mul(@bigint.BigInt::from_int64(right)),
operation,
)
}