use core::cmp::Ordering;
use core::ops::{Add, AddAssign};
use dashu_base::{AbsOrd, Approximation, BitTest, EstimatedLog2, Sign, Signed, UnsignedAbs};
use dashu_int::{IBig, UBig, Word};
use crate::FBig;
pub mod mode {
#[derive(Clone, Copy)]
pub struct Zero;
#[derive(Clone, Copy)]
pub struct Away;
#[derive(Clone, Copy)]
pub struct Up;
#[derive(Clone, Copy)]
pub struct Down;
#[derive(Clone, Copy)]
pub struct HalfEven;
#[derive(Clone, Copy)]
pub struct HalfAway;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rounding {
NoOp,
AddOne,
SubOne,
}
pub type Rounded<T> = Approximation<T, Rounding>;
pub trait Round: Copy {
type Reverse: Round;
fn round_low_part<F: FnOnce() -> Ordering>(
integer: &IBig,
low_sign: Sign,
low_half_test: F,
) -> Rounding;
#[inline]
fn round_fract<const B: Word>(integer: &IBig, fract: IBig, precision: usize) -> Rounding {
debug_assert!(fract.clone().unsigned_abs() < UBig::from_word(B).pow(precision));
if fract.is_zero() {
return Rounding::NoOp;
}
let (fsign, fmag) = fract.into_parts();
let test = || {
let (lb, ub) = fmag.log2_bounds();
let (b_lb, b_ub) = B.log2_bounds();
if lb + 0.999 > b_ub * precision as f32 {
Ordering::Greater
} else if ub + 1.001 < b_lb * precision as f32 {
Ordering::Less
} else {
(fmag << 1).cmp(&UBig::from_word(B).pow(precision))
}
};
Self::round_low_part::<_>(integer, fsign, test)
}
#[inline]
fn round_ratio(integer: &IBig, num: IBig, den: &IBig) -> Rounding {
assert!(!den.is_zero() && num.abs_cmp(den).is_le());
if num.is_zero() {
return Rounding::NoOp;
}
let (nsign, nmag) = num.into_parts();
Self::round_low_part::<_>(integer, nsign * den.sign(), || {
if den.is_positive() {
IBig::from(nmag << 1).cmp(den)
} else {
den.cmp(&-(nmag << 1))
}
})
}
}
pub trait ErrorBounds: Round {
fn error_bounds<const B: Word>(f: &FBig<Self, B>)
-> (FBig<Self, B>, FBig<Self, B>, bool, bool);
}
impl Round for mode::Zero {
type Reverse = mode::Away;
#[inline]
fn round_low_part<F: FnOnce() -> Ordering>(
integer: &IBig,
low_sign: Sign,
_low_half_test: F,
) -> Rounding {
if integer.is_zero() {
return Rounding::NoOp;
}
match (integer.sign(), low_sign) {
(Sign::Positive, Sign::Positive) | (Sign::Negative, Sign::Negative) => Rounding::NoOp,
(Sign::Positive, Sign::Negative) => Rounding::SubOne,
(Sign::Negative, Sign::Positive) => Rounding::AddOne,
}
}
}
impl ErrorBounds for mode::Zero {
#[inline]
fn error_bounds<const B: Word>(
f: &FBig<Self, B>,
) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
if f.precision() == 0 {
(FBig::ZERO, FBig::ZERO, true, true)
} else if f.repr().is_zero() {
(f.ulp(), f.ulp(), false, false)
} else {
match f.repr().sign() {
Sign::Positive => (FBig::ZERO, f.ulp(), true, false),
Sign::Negative => (f.ulp(), FBig::ZERO, false, true),
}
}
}
}
impl Round for mode::Away {
type Reverse = mode::Zero;
#[inline]
fn round_low_part<F: FnOnce() -> Ordering>(
integer: &IBig,
low_sign: Sign,
_low_half_test: F,
) -> Rounding {
if integer.is_zero() {
match low_sign {
Sign::Positive => Rounding::AddOne,
Sign::Negative => Rounding::SubOne,
}
} else {
match (integer.sign(), low_sign) {
(Sign::Positive, Sign::Positive) => Rounding::AddOne,
(Sign::Negative, Sign::Negative) => Rounding::SubOne,
(Sign::Positive, Sign::Negative) | (Sign::Negative, Sign::Positive) => {
Rounding::NoOp
}
}
}
}
}
impl ErrorBounds for mode::Away {
#[inline]
fn error_bounds<const B: Word>(
f: &FBig<Self, B>,
) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
if f.precision() == 0 && f.repr().is_zero() {
(FBig::ZERO, FBig::ZERO, true, true)
} else {
match f.repr().sign() {
Sign::Positive => (f.ulp(), FBig::ZERO, false, true),
Sign::Negative => (FBig::ZERO, f.ulp(), true, false),
}
}
}
}
impl Round for mode::Down {
type Reverse = mode::Up;
#[inline]
fn round_low_part<F: FnOnce() -> Ordering>(
_integer: &IBig,
low_sign: Sign,
_low_half_test: F,
) -> Rounding {
if low_sign == Sign::Negative {
Rounding::SubOne
} else {
Rounding::NoOp
}
}
}
impl ErrorBounds for mode::Down {
#[inline]
fn error_bounds<const B: Word>(
f: &FBig<Self, B>,
) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
(FBig::ZERO, f.ulp(), true, false)
}
}
impl Round for mode::Up {
type Reverse = mode::Down;
#[inline]
fn round_low_part<F: FnOnce() -> Ordering>(
_integer: &IBig,
low_sign: Sign,
_low_half_test: F,
) -> Rounding {
if low_sign == Sign::Positive {
Rounding::AddOne
} else {
Rounding::NoOp
}
}
}
impl ErrorBounds for mode::Up {
#[inline]
fn error_bounds<const B: Word>(
f: &FBig<Self, B>,
) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
(f.ulp(), FBig::ZERO, false, true)
}
}
impl Round for mode::HalfAway {
type Reverse = Self;
#[inline]
fn round_low_part<F: FnOnce() -> Ordering>(
integer: &IBig,
low_sign: Sign,
low_half_test: F,
) -> Rounding {
match low_half_test() {
Ordering::Less => Rounding::NoOp,
Ordering::Equal => {
if integer >= &IBig::ZERO && low_sign == Sign::Positive {
Rounding::AddOne
} else if integer <= &IBig::ZERO && low_sign == Sign::Negative {
Rounding::SubOne
} else {
Rounding::NoOp
}
}
Ordering::Greater => {
match low_sign {
Sign::Positive => Rounding::AddOne,
Sign::Negative => Rounding::SubOne,
}
}
}
}
}
impl ErrorBounds for mode::HalfAway {
#[inline]
fn error_bounds<const B: Word>(
f: &FBig<Self, B>,
) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
if f.precision() == 0 {
return (FBig::ZERO, FBig::ZERO, true, true);
}
let mut half_ulp = f.ulp();
half_ulp.repr.exponent -= 1;
half_ulp.repr.significand = UBig::from_word((B + 1) / 2).into(); let (incl_l, incl_r) = if f.repr.is_zero() {
(false, false)
} else if f.repr.sign() == Sign::Negative {
(false, true)
} else {
(true, false)
};
(half_ulp.clone(), half_ulp, incl_l, incl_r)
}
}
impl Round for mode::HalfEven {
type Reverse = Self;
#[inline]
fn round_low_part<F: FnOnce() -> Ordering>(
integer: &IBig,
low_sign: Sign,
low_half_test: F,
) -> Rounding {
match low_half_test() {
Ordering::Less => Rounding::NoOp,
Ordering::Equal => {
if integer.bit(0) {
match low_sign {
Sign::Positive => Rounding::AddOne,
Sign::Negative => Rounding::SubOne,
}
} else {
Rounding::NoOp
}
}
Ordering::Greater => {
match low_sign {
Sign::Positive => Rounding::AddOne,
Sign::Negative => Rounding::SubOne,
}
}
}
}
}
impl ErrorBounds for mode::HalfEven {
#[inline]
fn error_bounds<const B: Word>(
f: &FBig<Self, B>,
) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
if f.precision() == 0 {
return (FBig::ZERO, FBig::ZERO, true, true);
}
let mut half_ulp = f.ulp();
half_ulp.repr.exponent -= 1;
half_ulp.repr.significand = UBig::from_word((B + 1) / 2).into(); let incl = f.repr.significand.bit(0);
(half_ulp.clone(), half_ulp, incl, incl)
}
}
impl Add<Rounding> for IBig {
type Output = IBig;
#[inline]
fn add(self, rhs: Rounding) -> Self::Output {
match rhs {
Rounding::NoOp => self,
Rounding::AddOne => self + IBig::ONE,
Rounding::SubOne => self - IBig::ONE,
}
}
}
impl Add<Rounding> for &IBig {
type Output = IBig;
#[inline]
fn add(self, rhs: Rounding) -> Self::Output {
match rhs {
Rounding::NoOp => self.clone(),
Rounding::AddOne => self + IBig::ONE,
Rounding::SubOne => self - IBig::ONE,
}
}
}
impl AddAssign<Rounding> for IBig {
#[inline]
fn add_assign(&mut self, rhs: Rounding) {
match rhs {
Rounding::NoOp => {}
Rounding::AddOne => *self += IBig::ONE,
Rounding::SubOne => *self -= IBig::ONE,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::{mode::*, Rounding::*};
#[test]
fn test_from_fract() {
#[rustfmt::skip]
fn test_all_rounding<const B: Word, const D: usize>(
input: &(i32, i32, Rounding, Rounding, Rounding, Rounding, Rounding, Rounding),
) {
let (value, fract, rnd_zero, rnd_away, rnd_up, rnd_down, rnd_halfeven, rnd_halfaway) = *input;
let (value, fract) = (IBig::from(value), IBig::from(fract));
assert_eq!(Zero::round_fract::<B>(&value, fract.clone(), D), rnd_zero);
assert_eq!(Away::round_fract::<B>(&value, fract.clone(), D), rnd_away);
assert_eq!(Up::round_fract::<B>(&value, fract.clone(), D), rnd_up);
assert_eq!(Down::round_fract::<B>(&value, fract.clone(), D), rnd_down);
assert_eq!(HalfEven::round_fract::<B>(&value, fract.clone(), D), rnd_halfeven);
assert_eq!(HalfAway::round_fract::<B>(&value, fract, D), rnd_halfaway);
}
#[rustfmt::skip]
let binary_cases = [
( 0, 3, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 0, 2, NoOp , AddOne, AddOne, NoOp , NoOp , AddOne),
( 0, 1, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 0, 0, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, -1, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
( 0, -2, NoOp , SubOne, NoOp , SubOne, NoOp , SubOne),
( 0, -3, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
( 1, 3, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, 2, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, 1, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 1, 0, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, -1, SubOne, NoOp , NoOp , SubOne, NoOp , NoOp ),
( 1, -2, SubOne, NoOp , NoOp , SubOne, SubOne, NoOp ),
( 1, -3, SubOne, NoOp , NoOp , SubOne, SubOne, SubOne),
(-1, 3, AddOne, NoOp , AddOne, NoOp , AddOne, AddOne),
(-1, 2, AddOne, NoOp , AddOne, NoOp , AddOne, NoOp ),
(-1, 1, AddOne, NoOp , AddOne, NoOp , NoOp , NoOp ),
(-1, 0, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, -1, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
(-1, -2, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
(-1, -3, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
];
binary_cases.iter().for_each(test_all_rounding::<2, 2>);
#[rustfmt::skip]
let tenary_cases = [
( 0, 2, NoOp, AddOne, AddOne, NoOp , AddOne, AddOne),
( 0, 1, NoOp, AddOne, AddOne, NoOp , NoOp , NoOp ),
( 0, 0, NoOp, NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, -1, NoOp, SubOne, NoOp , SubOne, NoOp , NoOp ),
( 0, -2, NoOp, SubOne, NoOp , SubOne, SubOne, SubOne),
( 1, 2, NoOp, AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, 1, NoOp, AddOne, AddOne, NoOp , NoOp , NoOp ),
( 1, 0, NoOp, NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, -1, SubOne, NoOp , NoOp , SubOne, NoOp , NoOp ),
( 1, -2, SubOne, NoOp , NoOp , SubOne, SubOne, SubOne),
(-1, 2, AddOne, NoOp , AddOne, NoOp , AddOne, AddOne),
(-1, 1, AddOne, NoOp , AddOne, NoOp , NoOp , NoOp ),
(-1, 0, NoOp, NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, -1, NoOp, SubOne, NoOp , SubOne, NoOp , NoOp ),
(-1, -2, NoOp, SubOne, NoOp , SubOne, SubOne, SubOne),
];
tenary_cases.iter().for_each(test_all_rounding::<3, 1>);
#[rustfmt::skip]
let decimal_cases = [
( 0, 7, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 0, 5, NoOp , AddOne, AddOne, NoOp , NoOp , AddOne),
( 0, 2, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 0, 0, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, -2, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
( 0, -5, NoOp , SubOne, NoOp , SubOne, NoOp , SubOne),
( 0, -7, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
( 1, 7, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, 5, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, 2, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 1, 0, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, -2, SubOne, NoOp , NoOp , SubOne, NoOp , NoOp ),
( 1, -5, SubOne, NoOp , NoOp , SubOne, SubOne, NoOp ),
( 1, -7, SubOne, NoOp , NoOp , SubOne, SubOne, SubOne),
(-1, 7, AddOne, NoOp , AddOne, NoOp , AddOne, AddOne),
(-1, 5, AddOne, NoOp , AddOne, NoOp , AddOne, NoOp ),
(-1, 2, AddOne, NoOp , AddOne, NoOp , NoOp , NoOp ),
(-1, 0, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, -2, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
(-1, -5, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
(-1, -7, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
];
decimal_cases.iter().for_each(test_all_rounding::<10, 1>);
}
#[test]
fn test_from_ratio() {
#[rustfmt::skip]
fn test_all_rounding(
input: &(i32, i32, i32, Rounding, Rounding, Rounding, Rounding, Rounding, Rounding),
) {
let (value, num, den, rnd_zero, rnd_away, rnd_up, rnd_down, rnd_halfeven, rnd_halfaway) = *input;
let (value, num, den) = (IBig::from(value), IBig::from(num), IBig::from(den));
assert_eq!(Zero::round_ratio(&value, num.clone(), &den), rnd_zero);
assert_eq!(Away::round_ratio(&value, num.clone(), &den), rnd_away);
assert_eq!(Up::round_ratio(&value, num.clone(), &den), rnd_up);
assert_eq!(Down::round_ratio(&value, num.clone(), &den), rnd_down);
assert_eq!(HalfEven::round_ratio(&value, num.clone(), &den), rnd_halfeven);
assert_eq!(HalfAway::round_ratio(&value, num, &den), rnd_halfaway);
}
#[rustfmt::skip]
let test_cases = [
( 0, 0, 2, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, 1, 2, NoOp , AddOne, AddOne, NoOp , NoOp , AddOne),
( 0, -1, 2, NoOp , SubOne, NoOp , SubOne, NoOp , SubOne),
( 0, 0, -2, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, 1, -2, NoOp , SubOne, NoOp , SubOne, NoOp , SubOne),
( 0, -1, -2, NoOp , AddOne, AddOne, NoOp , NoOp , AddOne),
( 1, 0, 2, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, 1, 2, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, -1, 2, SubOne, NoOp , NoOp , SubOne, SubOne, NoOp ),
( 1, 0, -2, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, 1, -2, SubOne, NoOp , NoOp , SubOne, SubOne, NoOp ),
( 1, -1, -2, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
(-1, 0, 2, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, 1, 2, AddOne, NoOp , AddOne, NoOp , AddOne, NoOp ),
(-1, -1, 2, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
(-1, 0, -2, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, 1, -2, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
(-1, -1, -2, AddOne, NoOp , AddOne, NoOp , AddOne, NoOp ),
( 0, -2, 3, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
( 0, -1, 3, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
( 0, 0, 3, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, 1, 3, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 0, 2, 3, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 0, -2, -3, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 0, -1, -3, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 0, 0, -3, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 0, 1, -3, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
( 0, 2, -3, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
( 1, -2, 3, SubOne, NoOp , NoOp , SubOne, SubOne, SubOne),
( 1, -1, 3, SubOne, NoOp , NoOp , SubOne, NoOp , NoOp ),
( 1, 0, 3, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, 1, 3, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 1, 2, 3, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, -2, -3, NoOp , AddOne, AddOne, NoOp , AddOne, AddOne),
( 1, -1, -3, NoOp , AddOne, AddOne, NoOp , NoOp , NoOp ),
( 1, 0, -3, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
( 1, 1, -3, SubOne, NoOp , NoOp , SubOne, NoOp , NoOp ),
( 1, 2, -3, SubOne, NoOp , NoOp , SubOne, SubOne, SubOne),
(-1, -2, 3, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
(-1, -1, 3, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
(-1, 0, 3, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, 1, 3, AddOne, NoOp , AddOne, NoOp , NoOp , NoOp ),
(-1, 2, 3, AddOne, NoOp , AddOne, NoOp , AddOne, AddOne),
(-1, -2, -3, AddOne, NoOp , AddOne, NoOp , AddOne, AddOne),
(-1, -1, -3, AddOne, NoOp , AddOne, NoOp , NoOp , NoOp ),
(-1, 0, -3, NoOp , NoOp , NoOp , NoOp , NoOp , NoOp ),
(-1, 1, -3, NoOp , SubOne, NoOp , SubOne, NoOp , NoOp ),
(-1, 2, -3, NoOp , SubOne, NoOp , SubOne, SubOne, SubOne),
];
test_cases.iter().for_each(test_all_rounding);
}
}