use crate::{
util::{
rangeint::{RFrom, RInto},
t::{NoUnits, NoUnits128, C, C128},
},
Unit,
};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RoundMode {
Ceil,
Floor,
Expand,
Trunc,
HalfCeil,
HalfFloor,
HalfExpand,
HalfTrunc,
HalfEven,
}
impl RoundMode {
pub(crate) fn round_by_unit_in_nanoseconds(
self,
quantity: impl RInto<NoUnits128>,
unit: Unit,
increment: impl RInto<NoUnits128>,
) -> NoUnits128 {
let quantity = quantity.rinto();
let increment = unit.nanoseconds() * increment.rinto();
let rounded = self.round(quantity, increment);
rounded
}
fn round(
self,
quantity: impl RInto<NoUnits128>,
increment: impl RInto<NoUnits128>,
) -> NoUnits128 {
fn inner(
mode: RoundMode,
quantity: NoUnits128,
increment: NoUnits128,
) -> NoUnits128 {
let mut quotient = quantity.div_ceil(increment);
let remainder = quantity.rem_ceil(increment);
if remainder == 0 {
return quantity;
}
let sign = if remainder < 0 { C128(-1) } else { C128(1) };
let tiebreaker = (remainder * C128(2)).abs();
let tie = tiebreaker == increment;
let expand_is_nearer = tiebreaker > increment;
match mode {
RoundMode::Ceil => {
if sign > 0 {
quotient += sign;
}
}
RoundMode::Floor => {
if sign < 0 {
quotient += sign;
}
}
RoundMode::Expand => {
quotient += sign;
}
RoundMode::Trunc => {}
RoundMode::HalfCeil => {
if expand_is_nearer || (tie && sign > 0) {
quotient += sign;
}
}
RoundMode::HalfFloor => {
if expand_is_nearer || (tie && sign < 0) {
quotient += sign;
}
}
RoundMode::HalfExpand => {
if expand_is_nearer || tie {
quotient += sign;
}
}
RoundMode::HalfTrunc => {
if expand_is_nearer {
quotient += sign;
}
}
RoundMode::HalfEven => {
if expand_is_nearer || (tie && quotient % C(2) == 1) {
quotient += sign;
}
}
}
quotient.saturating_mul(increment)
}
inner(self, quantity.rinto(), increment.rinto())
}
pub(crate) fn round_float(
self,
quantity: f64,
increment: NoUnits128,
) -> NoUnits128 {
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
let quotient = quantity / (increment.get() as f64);
let rounded = match self {
RoundMode::Ceil => quotient.ceil(),
RoundMode::Floor => quotient.floor(),
RoundMode::Expand => {
if quotient < 0.0 {
quotient.floor()
} else {
quotient.ceil()
}
}
RoundMode::Trunc => quotient.trunc(),
RoundMode::HalfCeil => {
if quotient % 1.0 == 0.5 {
quotient.ceil()
} else {
quotient.round()
}
}
RoundMode::HalfFloor => {
if quotient % 1.0 == 0.5 {
quotient.floor()
} else {
quotient.round()
}
}
RoundMode::HalfExpand => {
quotient.signum() * quotient.abs().round()
}
RoundMode::HalfTrunc => {
if quotient % 1.0 == 0.5 {
quotient.trunc()
} else {
quotient.round()
}
}
RoundMode::HalfEven => {
if quotient % 1.0 == 0.5 {
quotient.trunc() + (quotient % 2.0)
} else {
quotient.round()
}
}
};
let rounded = NoUnits::new(rounded as i64).unwrap();
NoUnits128::rfrom(rounded.saturating_mul(increment))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_to_increment_half_expand_ad_hoc() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
i64::from(RoundMode::HalfExpand.round(quantity, increment))
};
assert_eq!(26, round(20, 13));
assert_eq!(0, round(29, 60));
assert_eq!(60, round(30, 60));
assert_eq!(60, round(31, 60));
assert_eq!(0, round(3, 7));
assert_eq!(7, round(4, 7));
}
#[test]
fn round_to_increment_temporal_table_ceil() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::Ceil.round(quantity, increment).into()
};
assert_eq!(-10, round(-15, 10));
assert_eq!(0, round(-5, 10));
assert_eq!(10, round(4, 10));
assert_eq!(10, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(20, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_floor() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::Floor.round(quantity, increment).into()
};
assert_eq!(-20, round(-15, 10));
assert_eq!(-10, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(0, round(5, 10));
assert_eq!(0, round(6, 10));
assert_eq!(10, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_expand() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::Expand.round(quantity, increment).into()
};
assert_eq!(-20, round(-15, 10));
assert_eq!(-10, round(-5, 10));
assert_eq!(10, round(4, 10));
assert_eq!(10, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(20, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_trunc() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::Trunc.round(quantity, increment).into()
};
assert_eq!(-10, round(-15, 10));
assert_eq!(0, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(0, round(5, 10));
assert_eq!(0, round(6, 10));
assert_eq!(10, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_half_ceil() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::HalfCeil.round(quantity, increment).into()
};
assert_eq!(-10, round(-15, 10));
assert_eq!(0, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(10, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(20, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_half_floor() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::HalfFloor.round(quantity, increment).into()
};
assert_eq!(-20, round(-15, 10));
assert_eq!(-10, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(0, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(10, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_half_expand() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::HalfExpand.round(quantity, increment).into()
};
assert_eq!(-20, round(-15, 10));
assert_eq!(-10, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(10, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(20, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_half_trunc() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::HalfTrunc.round(quantity, increment).into()
};
assert_eq!(-10, round(-15, 10));
assert_eq!(0, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(0, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(10, round(15, 10));
}
#[test]
fn round_to_increment_temporal_table_half_even() {
let round = |quantity: i64, increment: i64| -> i64 {
let quantity = NoUnits::new(quantity).unwrap();
let increment = NoUnits::new(increment).unwrap();
RoundMode::HalfEven.round(quantity, increment).into()
};
assert_eq!(-20, round(-15, 10));
assert_eq!(0, round(-5, 10));
assert_eq!(0, round(4, 10));
assert_eq!(0, round(5, 10));
assert_eq!(10, round(6, 10));
assert_eq!(20, round(15, 10));
}
}