use std::ops::Rem;
use rug::Integer;
use crate::fixed::{Exceptions, Fixed};
use crate::rfloat::{RFloat, RFloatContext};
use crate::{Real, RoundingContext, RoundingMode, Split};
#[derive(Clone, Debug)]
pub enum Overflow {
Wrap,
Saturate,
}
#[derive(Clone, Debug)]
pub struct FixedContext {
pub(crate) signed: bool,
pub(crate) scale: isize,
pub(crate) nbits: usize,
pub(crate) rm: RoundingMode,
pub(crate) overflow: Overflow,
}
impl FixedContext {
pub fn new(signed: bool, scale: isize, nbits: usize) -> Self {
assert!(
(signed && nbits >= 2) || (!signed && nbits >= 1),
"insufficient number of representation bits"
);
Self {
signed,
scale,
nbits,
rm: RoundingMode::ToZero,
overflow: Overflow::Wrap,
}
}
pub fn with_rounding_mode(mut self, rm: RoundingMode) -> Self {
self.rm = rm;
self
}
pub fn with_overflow(mut self, overflow: Overflow) -> Self {
self.overflow = overflow;
self
}
pub fn maxval(&self) -> Fixed {
if self.signed {
let c = (Integer::from(1) << (self.nbits - 1)) - 1;
Fixed {
num: RFloat::Real(false, self.scale, c),
flags: Default::default(),
ctx: self.clone(),
}
} else {
let c = (Integer::from(1) << self.nbits) - 1;
Fixed {
num: RFloat::Real(false, self.scale, c),
flags: Default::default(),
ctx: self.clone(),
}
}
}
pub fn minval(&self) -> Fixed {
if self.signed {
let c = Integer::from(1) << (self.nbits - 1);
Fixed {
num: RFloat::Real(true, self.scale, c),
flags: Default::default(),
ctx: self.clone(),
}
} else {
Fixed {
num: RFloat::zero(),
flags: Default::default(),
ctx: self.clone(),
}
}
}
pub fn zero(&self) -> Fixed {
Fixed {
num: RFloat::zero(),
flags: Default::default(),
ctx: self.clone(),
}
}
pub fn quantum(&self) -> Fixed {
Fixed {
num: RFloat::Real(false, -self.scale, Integer::from(1)),
flags: Default::default(),
ctx: self.clone(),
}
}
}
impl FixedContext {
fn round_wrap(&self, val: RFloat) -> RFloat {
let offset = val.exp().unwrap() - self.scale;
let div = Integer::from(1) << self.nbits;
let c = val.c().unwrap() << offset;
if self.signed {
let shift = Integer::from(1) << (self.nbits - 1);
let m = if val.sign().unwrap() { -c } else { c };
let wrapped = (m + shift.clone()).rem(div) - shift;
RFloat::Real(wrapped.is_negative(), self.scale, wrapped.abs())
} else {
let wrapped = c.rem(div);
RFloat::Real(false, self.scale, wrapped)
}
}
}
impl RoundingContext for FixedContext {
type Format = Fixed;
fn round<T: Real>(&self, val: &T) -> Self::Format {
if val.is_zero() {
Fixed {
num: RFloat::zero(),
flags: Default::default(),
ctx: self.clone(),
}
} else if val.is_infinite() {
match val.sign() {
Some(true) => self.maxval(),
_ => self.minval(),
}
} else if val.is_nar() {
Fixed {
num: RFloat::zero(),
flags: Exceptions {
invalid: true,
..Default::default()
},
ctx: self.clone(),
}
} else {
let (p, n) = RFloatContext::new()
.with_min_n(self.scale - 1)
.round_params(val);
let split = Split::new(val, p, n);
let inexact = !split.is_exact();
let rounded = RFloatContext::round_finalize(split, self.rm);
if rounded.is_zero() {
return Fixed {
num: RFloat::zero(),
flags: Exceptions {
inexact,
..Default::default()
},
ctx: self.clone(),
};
}
let exp = rounded.exp().unwrap();
assert!(
exp >= self.scale,
"unexpected exponent, scale: {}, num: {:?}",
self.scale,
rounded
);
let maxval = self.maxval();
let minval = self.minval();
if rounded > maxval.num {
Fixed {
num: match self.overflow {
Overflow::Wrap => self.round_wrap(rounded),
Overflow::Saturate => maxval.num,
},
flags: Exceptions {
inexact,
overflow: true,
..Default::default()
},
ctx: self.clone(),
}
} else if rounded < minval.num {
Fixed {
num: match self.overflow {
Overflow::Wrap => self.round_wrap(rounded),
Overflow::Saturate => minval.num,
},
flags: Exceptions {
inexact,
underflow: false,
..Default::default()
},
ctx: self.clone(),
}
} else {
Fixed {
num: rounded,
flags: Exceptions {
inexact,
..Default::default()
},
ctx: self.clone(),
}
}
}
}
}