use alloc::rc::Rc;
use core::cell::RefCell;
use core::cmp::Ordering;
use core::str::FromStr;
use dashu_base::{AbsOrd, ConversionError, EstimatedLog2, ParseError, Sign, Signed};
use dashu_int::{IBig, UBig};
use crate::error::panic_unlimited_precision;
use crate::fbig::FBig;
use crate::math::cache::ConstCache;
use crate::repr::{Context, Repr, Word};
use crate::round::{mode, Round, Rounded};
use crate::utils::digit_len;
pub struct CachedFBig<R: Round = mode::Zero, const B: Word = 2> {
pub(crate) fbig: FBig<R, B>,
pub(crate) cache: Rc<RefCell<ConstCache>>,
}
impl<R: Round, const B: Word> CachedFBig<R, B> {
#[inline]
pub fn new(value: FBig<R, B>, cache: Rc<RefCell<ConstCache>>) -> Self {
Self { fbig: value, cache }
}
#[inline]
pub fn from_repr(repr: Repr<B>, context: Context<R>, cache: Rc<RefCell<ConstCache>>) -> Self {
Self {
fbig: FBig::new(repr, context),
cache,
}
}
#[inline]
pub fn with_cache(repr: Repr<B>, context: Context<R>) -> Self {
Self::from_repr(repr, context, Rc::new(RefCell::new(ConstCache::new())))
}
#[inline]
pub(crate) fn from_fbig(fbig: FBig<R, B>, cache: &Rc<RefCell<ConstCache>>) -> Self {
Self {
fbig,
cache: Rc::clone(cache),
}
}
#[inline]
pub fn as_fbig(&self) -> &FBig<R, B> {
&self.fbig
}
#[inline]
pub fn into_fbig(self) -> FBig<R, B> {
self.fbig
}
#[inline]
pub fn cache(&self) -> impl core::ops::Deref<Target = ConstCache> + '_ {
self.cache.borrow()
}
#[inline]
pub fn clear_cache(&self) {
self.cache.borrow_mut().clear();
}
pub fn pi(precision: usize, cache: &Rc<RefCell<ConstCache>>) -> Self {
let fbig = {
let mut c = cache.borrow_mut();
Context::<R>::new(precision).pi::<B>(Some(&mut *c)).value()
};
Self::from_fbig(fbig, cache)
}
#[inline]
pub const fn precision(&self) -> usize {
self.fbig.context.precision
}
#[inline]
pub fn digits(&self) -> usize {
self.fbig.repr.digits()
}
#[inline]
pub const fn context(&self) -> Context<R> {
self.fbig.context
}
#[inline]
pub const fn repr(&self) -> &Repr<B> {
&self.fbig.repr
}
#[inline]
pub fn into_repr(self) -> Repr<B> {
self.fbig.repr
}
#[inline]
pub const fn sign(&self) -> Sign {
self.fbig.repr.sign()
}
pub fn with_precision(&self, precision: usize) -> Rounded<Self> {
self.fbig
.clone()
.with_precision(precision)
.map(|f| Self::from_fbig(f, &self.cache))
}
pub fn with_rounding<NewR: Round>(&self) -> CachedFBig<NewR, B> {
CachedFBig::from_fbig(self.fbig.clone().with_rounding::<NewR>(), &self.cache)
}
}
impl<R: Round, const B: Word> CachedFBig<R, B> {
pub fn ulp(&self) -> Self {
if self.fbig.context.precision == 0 {
panic_unlimited_precision();
}
let repr = Repr {
significand: dashu_int::IBig::ONE,
exponent: self.fbig.repr.exponent + self.fbig.repr.digits() as isize
- self.fbig.context.precision as isize,
};
Self::from_repr(repr, self.fbig.context, Rc::clone(&self.cache))
}
pub fn to_int(&self) -> Rounded<dashu_int::IBig> {
self.fbig.clone().to_int()
}
pub fn to_f32(&self) -> Rounded<f32> {
self.fbig.clone().to_f32()
}
pub fn to_f64(&self) -> Rounded<f64> {
self.fbig.clone().to_f64()
}
pub fn from_parts(significand: dashu_int::IBig, exponent: isize) -> Self {
let precision = digit_len::<B>(&significand).max(1);
let repr = Repr::new(significand, exponent);
Self::with_cache(repr, Context::new(precision))
}
}
impl<R: Round, const B: Word> From<FBig<R, B>> for CachedFBig<R, B> {
#[inline]
fn from(fbig: FBig<R, B>) -> Self {
Self::new(fbig, Rc::new(RefCell::new(ConstCache::new())))
}
}
impl<R: Round, const B: Word> From<CachedFBig<R, B>> for FBig<R, B> {
#[inline]
fn from(cached: CachedFBig<R, B>) -> Self {
cached.into_fbig()
}
}
impl<R: Round, const B: Word> FBig<R, B> {
#[inline]
pub fn into_cached(self, cache: Rc<RefCell<ConstCache>>) -> CachedFBig<R, B> {
CachedFBig::new(self, cache)
}
}
impl<R: Round, const B: Word> FromStr for CachedFBig<R, B> {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, ParseError> {
Ok(FBig::from_str(s)?.into())
}
}
impl<R: Round, const B: Word> From<UBig> for CachedFBig<R, B> {
#[inline]
fn from(n: UBig) -> Self {
FBig::from(n).into()
}
}
impl<R: Round, const B: Word> From<IBig> for CachedFBig<R, B> {
#[inline]
fn from(n: IBig) -> Self {
FBig::from(n).into()
}
}
macro_rules! impl_from_int_for_cached_fbig {
($($t:ty)*) => {$(
impl<R: Round, const B: Word> From<$t> for CachedFBig<R, B> {
#[inline]
fn from(value: $t) -> Self {
FBig::from(value).into()
}
}
)*};
}
impl_from_int_for_cached_fbig!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
impl<R: Round> TryFrom<f32> for CachedFBig<R, 2> {
type Error = ConversionError;
#[inline]
fn try_from(value: f32) -> Result<Self, Self::Error> {
FBig::try_from(value).map(Self::from)
}
}
impl<R: Round> TryFrom<f64> for CachedFBig<R, 2> {
type Error = ConversionError;
#[inline]
fn try_from(value: f64) -> Result<Self, Self::Error> {
FBig::try_from(value).map(Self::from)
}
}
macro_rules! impl_try_from_cached_fbig_for_int {
($($t:ty)*) => {$(
impl<R: Round, const B: Word> TryFrom<CachedFBig<R, B>> for $t {
type Error = ConversionError;
#[inline]
fn try_from(value: CachedFBig<R, B>) -> Result<Self, Self::Error> {
value.fbig.try_into()
}
}
)*};
}
impl_try_from_cached_fbig_for_int!(
u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize UBig IBig
);
impl<R: Round> TryFrom<CachedFBig<R, 2>> for f32 {
type Error = ConversionError;
#[inline]
fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
value.fbig.try_into()
}
}
impl<R: Round> TryFrom<CachedFBig<R, 2>> for f64 {
type Error = ConversionError;
#[inline]
fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
value.fbig.try_into()
}
}
impl<R: Round, const B: Word> Clone for CachedFBig<R, B> {
#[inline]
fn clone(&self) -> Self {
Self {
fbig: self.fbig.clone(),
cache: Rc::clone(&self.cache),
}
}
}
impl<R: Round, const B: Word> Default for CachedFBig<R, B> {
#[inline]
fn default() -> Self {
Self::with_cache(Repr::zero(), Context::new(0))
}
}
impl<R: Round, const B: Word> core::fmt::Debug for CachedFBig<R, B> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CachedFBig")
.field("repr", &self.fbig.repr)
.field("precision", &self.fbig.context.precision)
.finish()
}
}
impl<R: Round, const B: Word> core::fmt::Display for CachedFBig<R, B> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.fbig, f)
}
}
impl<R: Round, const B: Word> core::fmt::LowerExp for CachedFBig<R, B> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerExp::fmt(&self.fbig, f)
}
}
impl<R: Round, const B: Word> core::fmt::UpperExp for CachedFBig<R, B> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::UpperExp::fmt(&self.fbig, f)
}
}
macro_rules! impl_cached_fmt_with_base {
($base:literal, $trait:ident) => {
impl<R: Round> core::fmt::$trait for CachedFBig<R, $base> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::$trait::fmt(&self.fbig, f)
}
}
};
}
impl_cached_fmt_with_base!(2, Binary);
impl_cached_fmt_with_base!(2, LowerHex);
impl_cached_fmt_with_base!(2, UpperHex);
impl_cached_fmt_with_base!(8, Octal);
impl_cached_fmt_with_base!(16, LowerHex);
impl_cached_fmt_with_base!(16, UpperHex);
impl<R1: Round, R2: Round, const B: Word> PartialEq<CachedFBig<R2, B>> for CachedFBig<R1, B> {
#[inline]
fn eq(&self, other: &CachedFBig<R2, B>) -> bool {
self.fbig.repr == other.fbig.repr
}
}
impl<R: Round, const B: Word> Eq for CachedFBig<R, B> {}
impl<R1: Round, R2: Round, const B: Word> PartialOrd<CachedFBig<R2, B>> for CachedFBig<R1, B> {
#[inline]
fn partial_cmp(&self, other: &CachedFBig<R2, B>) -> Option<Ordering> {
self.fbig.partial_cmp(&other.fbig)
}
}
impl<R: Round, const B: Word> Ord for CachedFBig<R, B> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.fbig.cmp(&other.fbig)
}
}
impl<R: Round, const B: Word> AbsOrd for CachedFBig<R, B> {
#[inline]
fn abs_cmp(&self, other: &Self) -> Ordering {
AbsOrd::abs_cmp(&self.fbig, &other.fbig)
}
}
impl<R: Round, const B: Word> AbsOrd<UBig> for CachedFBig<R, B> {
#[inline]
fn abs_cmp(&self, other: &UBig) -> Ordering {
AbsOrd::abs_cmp(&self.fbig, other)
}
}
impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for UBig {
#[inline]
fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
AbsOrd::abs_cmp(self, &other.fbig)
}
}
impl<R: Round, const B: Word> AbsOrd<IBig> for CachedFBig<R, B> {
#[inline]
fn abs_cmp(&self, other: &IBig) -> Ordering {
AbsOrd::abs_cmp(&self.fbig, other)
}
}
impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for IBig {
#[inline]
fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
AbsOrd::abs_cmp(self, &other.fbig)
}
}
impl<R: Round, const B: Word> Signed for CachedFBig<R, B> {
#[inline]
fn sign(&self) -> Sign {
self.fbig.sign()
}
}
impl<R: Round, const B: Word> EstimatedLog2 for CachedFBig<R, B> {
#[inline]
fn log2_bounds(&self) -> (f32, f32) {
EstimatedLog2::log2_bounds(&self.fbig)
}
#[inline]
fn log2_est(&self) -> f32 {
EstimatedLog2::log2_est(&self.fbig)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::round::mode;
use alloc::format;
fn handle() -> Rc<RefCell<ConstCache>> {
Rc::new(RefCell::new(ConstCache::new()))
}
fn fbig(n: i32, prec: usize) -> FBig<mode::HalfAway, 10> {
FBig::from_repr(Repr::new(n.into(), 0), Context::new(prec))
}
#[test]
fn test_pi_matches_fbig() {
for &precision in &[10usize, 50, 100] {
let h = handle();
let cached = CachedFBig::<mode::HalfAway, 10>::pi(precision, &h).into_fbig();
let direct = FBig::<mode::HalfAway, 10>::pi(precision);
assert_eq!(cached, direct, "pi mismatch at precision {precision}");
}
}
#[test]
fn test_transcendentals_match_fbig() {
let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
Repr::new(1234.into(), -3), Context::new(50),
);
let y = FBig::<mode::HalfAway, 10>::from_repr(Repr::new(1234.into(), -3), Context::new(50));
assert_eq!(x.clone().ln().into_fbig(), y.clone().ln());
assert_eq!(x.clone().exp().into_fbig(), y.clone().exp());
assert_eq!(x.clone().sin().into_fbig(), y.clone().sin());
assert_eq!(x.clone().cos().into_fbig(), y.clone().cos());
assert_eq!(x.clone().exp_m1().into_fbig(), y.clone().exp_m1());
assert_eq!(x.clone().ln_1p().into_fbig(), y.clone().ln_1p());
assert_eq!(x.powf(&x.clone()).into_fbig(), y.clone().powf(&y));
}
#[test]
fn test_cache_extension_matches_scratch() {
let h = handle();
let _pi_100 = CachedFBig::<mode::HalfAway, 10>::pi(100, &h);
let pi_1000 = CachedFBig::<mode::HalfAway, 10>::pi(1000, &h).into_fbig();
let direct = Context::<mode::HalfAway>::new(1000).pi::<10>(None).value();
assert_eq!(pi_1000, direct);
}
#[test]
fn test_cache_survives_arithmetic() {
let h = handle();
let a = CachedFBig::<mode::HalfAway, 10>::from_repr(
Repr::new(2.into(), 0),
Context::new(30),
h.clone(),
);
let b = CachedFBig::<mode::HalfAway, 10>::from_repr(
Repr::new(3.into(), 0),
Context::new(30),
h.clone(),
);
let sum_ln = (a.clone() + b.clone()).ln().into_fbig();
let expected = (fbig(2, 30) + fbig(3, 30)).ln();
assert_eq!(sum_ln, expected);
}
#[test]
fn test_arithmetic_matches_fbig() {
let a =
CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
let b =
CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(3.into(), 0), Context::new(20));
assert_eq!((a.clone() + b.clone()).into_fbig(), fbig(2, 20) + fbig(3, 20));
assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
}
#[test]
fn test_debug_compiles() {
let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
Repr::new(1234.into(), -3),
Context::new(50),
);
let s = format!("{:?}", x);
assert!(s.contains("CachedFBig"));
}
#[test]
fn test_arithmetic_with_fbig() {
let a =
CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
let b = fbig(3, 20);
let c = a.clone() + b.clone();
assert_eq!(c.into_fbig(), fbig(2, 20) + fbig(3, 20));
let d = b.clone() + a.clone();
assert_eq!(d.into_fbig(), fbig(3, 20) + fbig(2, 20));
assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
}
#[test]
fn test_arithmetic_with_primitives() {
let a =
CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
assert_eq!((a.clone() + 3u8).into_fbig(), fbig(2, 20) + 3u8);
assert_eq!((a.clone() - 3i32).into_fbig(), fbig(2, 20) - 3i32);
assert_eq!((a.clone() * 4u64).into_fbig(), fbig(2, 20) * 4u64);
assert_eq!((3u8 + a.clone()).into_fbig(), 3u8 + fbig(2, 20));
assert_eq!((10i32 - a.clone()).into_fbig(), 10i32 - fbig(2, 20));
}
#[test]
fn test_cache_size() {
let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
Repr::new(1234.into(), -3),
Context::new(50),
);
let _ = x.ln();
assert!(x.cache().total_terms() > 0);
assert!(x.cache().total_words() > 0);
}
#[test]
fn test_cache_clear() {
let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
Repr::new(1234.into(), -3),
Context::new(50),
);
let before_clear = x.ln().into_fbig();
assert!(x.cache().total_terms() > 0);
x.clear_cache();
assert_eq!(x.cache().total_terms(), 0);
assert_eq!(x.cache().total_words(), 0);
let after_clear = x.ln().into_fbig();
assert_eq!(after_clear, before_clear);
}
}