use num_traits::Zero;
use rug::Integer;
use crate::rfloat::RFloat;
use crate::round::RoundingDirection;
use crate::{Real, RoundingContext, RoundingMode, Split};
#[derive(Clone, Debug)]
pub struct RFloatContext {
max_p: Option<usize>,
min_n: Option<isize>,
rm: RoundingMode,
}
impl RFloatContext {
pub fn new() -> Self {
Self {
max_p: None,
min_n: None,
rm: RoundingMode::NearestTiesToEven,
}
}
pub fn with_max_p(mut self, max_p: usize) -> Self {
assert!(max_p >= 1, "minimum precision must be at least 1");
self.max_p = Some(max_p);
self
}
pub fn with_min_n(mut self, min_n: isize) -> Self {
self.min_n = Some(min_n);
self
}
pub fn with_rounding_mode(mut self, rm: RoundingMode) -> Self {
self.rm = rm;
self
}
pub fn without_max_p(mut self) -> Self {
self.max_p = None;
self
}
pub fn without_min_n(mut self) -> Self {
self.min_n = None;
self
}
pub fn round_params<T: Real>(&self, num: &T) -> (Option<usize>, isize) {
match (self.max_p, self.min_n) {
(None, None) => {
panic!(
"at least one rounding parameter must be specified: max_p={:?}, min_n={:?}",
self.max_p, self.min_n
);
}
(None, Some(min_n)) => {
(None, min_n)
}
(Some(max_p), None) => {
match num.e() {
Some(e) => (Some(max_p), e - (max_p as isize)),
None => (Some(max_p), 0),
}
}
(Some(max_p), Some(min_n)) => {
match num.e() {
Some(e) => {
let unbounded_n = e - (max_p as isize);
let n = std::cmp::max(min_n, unbounded_n);
(Some(max_p), n)
}
None => (Some(max_p), 0),
}
}
}
}
fn round_increment(
sign: bool,
c: &Integer,
half_bit: bool,
sticky_bit: bool,
rm: RoundingMode,
) -> bool {
let (is_nearest, rd) = rm.to_direction(sign);
match (is_nearest, half_bit, sticky_bit, rd) {
(_, false, false, _) => {
false
}
(true, false, _, _) => {
false
}
(true, true, true, _) => {
true
}
(true, true, false, RoundingDirection::ToZero) => {
false
}
(true, true, false, RoundingDirection::AwayZero) => {
true
}
(true, true, false, RoundingDirection::ToEven) => {
c.is_odd()
}
(true, true, false, RoundingDirection::ToOdd) => {
c.is_even()
}
(false, _, _, RoundingDirection::ToZero) => {
false
}
(false, _, _, RoundingDirection::AwayZero) => {
true
}
(false, _, _, RoundingDirection::ToEven) => {
c.is_odd()
}
(false, _, _, RoundingDirection::ToOdd) => {
c.is_even()
}
}
}
pub(crate) fn round_finalize(split: Split, rm: RoundingMode) -> RFloat {
let s = split.num().sign().unwrap();
let (mut exp, mut c) = match split.num().exp() {
Some(exp) => (exp, split.num().c().unwrap()), None => (split.split_pos() + 1, Integer::zero()),
};
let (halfway_bit, sticky_bit) = split.rs();
if Self::round_increment(s, &c, halfway_bit, sticky_bit, rm) {
c += 1;
match split.max_p() {
None => (),
Some(max_p) => {
let p = c.significant_bits() as usize;
if p > max_p {
c >>= 1;
exp += 1;
}
}
}
}
RFloat::Real(s, exp, c)
}
}
impl Default for RFloatContext {
fn default() -> Self {
Self::new()
}
}
impl RoundingContext for RFloatContext {
type Format = RFloat;
fn round<T: Real>(&self, num: &T) -> Self::Format {
assert!(
self.max_p.is_some() || self.min_n.is_some(),
"must specify either maximum precision or least absolute digit"
);
if num.is_zero() {
RFloat::zero()
} else if num.is_infinite() {
if num.is_negative().unwrap() {
RFloat::NegInfinity
} else {
RFloat::PosInfinity
}
} else if num.is_nar() {
RFloat::Nan
} else {
let (p, n) = self.round_params(num);
let split = Split::new(num, p, n);
let rounded = Self::round_finalize(split, self.rm);
rounded.canonicalize()
}
}
}