#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RoundingMode {
HalfToEven,
HalfAwayFromZero,
HalfTowardZero,
Trunc,
Floor,
Ceiling,
}
#[cfg(feature = "rounding-half-away-from-zero")]
pub const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::HalfAwayFromZero;
#[cfg(all(
not(feature = "rounding-half-away-from-zero"),
feature = "rounding-half-toward-zero"
))]
pub const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::HalfTowardZero;
#[cfg(all(
not(feature = "rounding-half-away-from-zero"),
not(feature = "rounding-half-toward-zero"),
feature = "rounding-trunc"
))]
pub const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::Trunc;
#[cfg(all(
not(feature = "rounding-half-away-from-zero"),
not(feature = "rounding-half-toward-zero"),
not(feature = "rounding-trunc"),
feature = "rounding-floor"
))]
pub const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::Floor;
#[cfg(all(
not(feature = "rounding-half-away-from-zero"),
not(feature = "rounding-half-toward-zero"),
not(feature = "rounding-trunc"),
not(feature = "rounding-floor"),
feature = "rounding-ceiling"
))]
pub const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::Ceiling;
#[cfg(not(any(
feature = "rounding-half-away-from-zero",
feature = "rounding-half-toward-zero",
feature = "rounding-trunc",
feature = "rounding-floor",
feature = "rounding-ceiling",
)))]
pub const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::HalfToEven;
#[inline(always)]
pub(crate) fn should_bump(
mode: RoundingMode,
cmp_r: ::core::cmp::Ordering,
q_is_odd: bool,
result_positive: bool,
) -> bool {
use ::core::cmp::Ordering;
match mode {
RoundingMode::HalfToEven => match cmp_r {
Ordering::Less => false,
Ordering::Greater => true,
Ordering::Equal => q_is_odd,
},
RoundingMode::HalfAwayFromZero => !matches!(cmp_r, Ordering::Less),
RoundingMode::HalfTowardZero => matches!(cmp_r, Ordering::Greater),
RoundingMode::Trunc => false,
RoundingMode::Floor => !result_positive,
RoundingMode::Ceiling => result_positive,
}
}
#[inline(always)]
pub(crate) fn apply_rounding(raw: i128, divisor: i128, mode: RoundingMode) -> i128 {
let quotient = raw / divisor;
let remainder = raw % divisor;
if remainder == 0 {
return quotient;
}
let abs_rem = remainder.unsigned_abs();
let abs_div = divisor.unsigned_abs();
let comp = abs_div - abs_rem;
let cmp_r = abs_rem.cmp(&comp);
let q_is_odd = (quotient & 1) != 0;
let result_positive = (raw < 0) == (divisor < 0);
if should_bump(mode, cmp_r, q_is_odd, result_positive) {
if result_positive { quotient + 1 } else { quotient - 1 }
} else {
quotient
}
}
#[cfg(test)]
pub(crate) const DEFAULT_IS_HALF_TO_EVEN: bool = matches!(
DEFAULT_ROUNDING_MODE,
RoundingMode::HalfToEven
);
#[cfg(test)]
mod tests {
use super::*;
fn modes() -> [RoundingMode; 6] {
[
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Trunc,
RoundingMode::Floor,
RoundingMode::Ceiling,
]
}
#[test]
fn zero_remainder_is_quotient_for_all_modes() {
for m in modes() {
assert_eq!(apply_rounding(20, 10, m), 2, "{m:?}");
assert_eq!(apply_rounding(-20, 10, m), -2, "{m:?}");
assert_eq!(apply_rounding(0, 10, m), 0, "{m:?}");
}
}
#[test]
fn half_to_even_ties() {
let m = RoundingMode::HalfToEven;
assert_eq!(apply_rounding(5, 10, m), 0); assert_eq!(apply_rounding(15, 10, m), 2); assert_eq!(apply_rounding(25, 10, m), 2); assert_eq!(apply_rounding(35, 10, m), 4); assert_eq!(apply_rounding(-5, 10, m), 0); assert_eq!(apply_rounding(-15, 10, m), -2); assert_eq!(apply_rounding(-25, 10, m), -2); assert_eq!(apply_rounding(-35, 10, m), -4); }
#[test]
fn half_away_from_zero_ties() {
let m = RoundingMode::HalfAwayFromZero;
assert_eq!(apply_rounding(5, 10, m), 1);
assert_eq!(apply_rounding(15, 10, m), 2);
assert_eq!(apply_rounding(25, 10, m), 3);
assert_eq!(apply_rounding(-5, 10, m), -1);
assert_eq!(apply_rounding(-15, 10, m), -2);
assert_eq!(apply_rounding(-25, 10, m), -3);
}
#[test]
fn half_toward_zero_ties() {
let m = RoundingMode::HalfTowardZero;
assert_eq!(apply_rounding(5, 10, m), 0);
assert_eq!(apply_rounding(15, 10, m), 1);
assert_eq!(apply_rounding(25, 10, m), 2);
assert_eq!(apply_rounding(-5, 10, m), 0);
assert_eq!(apply_rounding(-15, 10, m), -1);
assert_eq!(apply_rounding(-25, 10, m), -2);
}
#[test]
fn trunc_always_toward_zero() {
let m = RoundingMode::Trunc;
assert_eq!(apply_rounding(7, 10, m), 0);
assert_eq!(apply_rounding(9, 10, m), 0);
assert_eq!(apply_rounding(19, 10, m), 1);
assert_eq!(apply_rounding(-7, 10, m), 0);
assert_eq!(apply_rounding(-19, 10, m), -1);
}
#[test]
fn floor_toward_negative_infinity() {
let m = RoundingMode::Floor;
assert_eq!(apply_rounding(1, 10, m), 0);
assert_eq!(apply_rounding(7, 10, m), 0);
assert_eq!(apply_rounding(9, 10, m), 0);
assert_eq!(apply_rounding(-1, 10, m), -1);
assert_eq!(apply_rounding(-7, 10, m), -1);
assert_eq!(apply_rounding(-19, 10, m), -2);
}
#[test]
fn ceiling_toward_positive_infinity() {
let m = RoundingMode::Ceiling;
assert_eq!(apply_rounding(1, 10, m), 1);
assert_eq!(apply_rounding(7, 10, m), 1);
assert_eq!(apply_rounding(19, 10, m), 2);
assert_eq!(apply_rounding(-1, 10, m), 0);
assert_eq!(apply_rounding(-7, 10, m), 0);
assert_eq!(apply_rounding(-19, 10, m), -1);
}
#[test]
fn non_half_goes_to_nearest() {
for m in [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
] {
assert_eq!(apply_rounding(4, 10, m), 0, "{m:?} 0.4");
assert_eq!(apply_rounding(6, 10, m), 1, "{m:?} 0.6");
assert_eq!(apply_rounding(-4, 10, m), 0, "{m:?} -0.4");
assert_eq!(apply_rounding(-6, 10, m), -1, "{m:?} -0.6");
}
}
}