#[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) const fn is_nearest_mode(mode: RoundingMode) -> bool {
matches!(
mode,
RoundingMode::HalfToEven | RoundingMode::HalfAwayFromZero | RoundingMode::HalfTowardZero
)
}
#[inline]
pub(crate) fn tiny_odd_compressing_directed<T>(raw: T, zero: T, one: T, mode: RoundingMode) -> T
where
T: Copy + PartialOrd + ::core::ops::Add<Output = T> + ::core::ops::Sub<Output = T>,
{
if is_nearest_mode(mode) {
return raw;
}
let positive = raw > zero;
match mode {
RoundingMode::Trunc => {
if positive {
raw - one
} else {
raw + one
}
}
RoundingMode::Floor => {
if positive {
raw - one
} else {
raw
}
}
RoundingMode::Ceiling => {
if positive {
raw
} else {
raw + one
}
}
_ => raw,
}
}
#[inline]
pub(crate) fn tiny_odd_expanding_directed<T>(raw: T, zero: T, one: T, mode: RoundingMode) -> T
where
T: Copy + PartialOrd + ::core::ops::Add<Output = T> + ::core::ops::Sub<Output = T>,
{
if is_nearest_mode(mode) {
return raw;
}
let positive = raw > zero;
match mode {
RoundingMode::Trunc => raw,
RoundingMode::Floor => {
if positive {
raw
} else {
raw - one
}
}
RoundingMode::Ceiling => {
if positive {
raw + one
} else {
raw
}
}
_ => raw,
}
}
#[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
}
}
const F64_INTEGER_THRESHOLD: f64 = 9_007_199_254_740_992.0_f64;
#[inline]
pub(crate) fn trunc_f64(x: f64) -> f64 {
if x.is_nan() {
return x;
}
let magnitude = if x < 0.0 { -x } else { x };
if magnitude >= F64_INTEGER_THRESHOLD {
return x;
}
let truncated = x as i128 as f64;
if truncated == 0.0 && x.is_sign_negative() {
-0.0
} else {
truncated
}
}
#[inline]
pub(crate) fn floor_f64(x: f64) -> f64 {
let truncated = trunc_f64(x);
if truncated > x {
truncated - 1.0
} else {
truncated
}
}
#[inline]
pub(crate) fn ceil_f64(x: f64) -> f64 {
let truncated = trunc_f64(x);
if truncated < x {
truncated + 1.0
} else {
truncated
}
}
#[inline]
pub(crate) fn round_half_away_f64(x: f64) -> f64 {
let truncated = trunc_f64(x);
let fraction = x - truncated;
if fraction >= 0.5 {
truncated + 1.0
} else if fraction <= -0.5 {
truncated - 1.0
} else {
truncated
}
}
#[inline]
pub(crate) fn round_half_even_f64(x: f64) -> f64 {
let truncated = trunc_f64(x);
let fraction = x - truncated;
if fraction > 0.5 {
truncated + 1.0
} else if fraction < -0.5 {
truncated - 1.0
} else if fraction == 0.5 {
if (truncated as i128) & 1 == 0 {
truncated
} else {
truncated + 1.0
}
} else if fraction == -0.5 {
if (truncated as i128) & 1 == 0 {
truncated
} else {
truncated - 1.0
}
} else {
truncated
}
}
#[inline]
pub(crate) fn round_half_toward_zero_f64(x: f64) -> f64 {
if x >= 0.0 {
ceil_f64(x - 0.5)
} else {
floor_f64(x + 0.5)
}
}
#[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");
}
}
}