use crate::RealSign;
#[cfg(any(feature = "cached-f32-approx", feature = "cached-f64-approx"))]
use crate::real::PrimitiveApproxCache;
use crate::{Computable, Problem, Rational, Real};
macro_rules! impl_integer_conversion {
($T:ty) => {
impl From<$T> for Real {
#[inline]
fn from(n: $T) -> Real {
if n == 0 {
return Real::zero();
}
if n == 1 {
return Real::one();
}
Real::new(Rational::from(n))
}
}
};
}
impl_integer_conversion!(i8);
impl_integer_conversion!(i16);
impl_integer_conversion!(i32);
impl_integer_conversion!(i64);
impl_integer_conversion!(i128);
impl_integer_conversion!(u8);
impl_integer_conversion!(u16);
impl_integer_conversion!(u32);
impl_integer_conversion!(u64);
impl_integer_conversion!(u128);
impl From<Rational> for Real {
fn from(rational: Rational) -> Real {
Real::new(rational)
}
}
impl TryFrom<f32> for Real {
type Error = Problem;
fn try_from(n: f32) -> Result<Real, Self::Error> {
let rational: Rational = n.try_into()?;
Ok(Real::new(rational))
}
}
impl TryFrom<f64> for Real {
type Error = Problem;
fn try_from(n: f64) -> Result<Real, Self::Error> {
let rational: Rational = n.try_into()?;
Ok(Real::new(rational))
}
}
impl Real {
#[inline]
pub(crate) fn fold_ref(&self) -> Computable {
use crate::real::Class;
let mut c = if self.rational.is_one() {
self.computable_clone()
} else if self.class == Class::One {
Computable::rational(self.rational.clone())
} else {
self.computable_clone()
.multiply_rational(self.rational.clone())
};
if let Some(s) = &self.signal {
c.abort(s.clone());
}
c
}
#[inline]
pub(crate) fn fold(self) -> Computable {
let crate::Real {
rational,
class,
computable,
signal,
primitive_approx_cache: _,
} = self;
if rational.is_one() {
let mut c = computable.unwrap_or_else(Computable::one);
if let Some(s) = signal {
c.abort(s.clone());
}
c
} else if class == crate::real::Class::One {
Computable::rational(rational)
} else {
let mut c = computable
.unwrap_or_else(Computable::one)
.multiply_rational(rational);
if let Some(s) = signal {
c.abort(s);
}
c
}
}
}
use crate::computable::Precision;
fn sig_exp_32(c: Computable, mut msd: Precision) -> (u32, u32) {
const SIG_BITS: u32 = 0x007f_ffff;
const OVERSIZE: u32 = SIG_BITS.next_power_of_two() << 1;
if msd <= -126 {
let sig = c
.approx(-149)
.magnitude()
.try_into()
.expect("Magnitude of the top bits should fit in a u32");
if sig > SIG_BITS {
(sig & SIG_BITS, 1)
} else {
(sig, 0)
}
} else {
let mut sig: u32 = c
.approx(msd - 24)
.magnitude()
.try_into()
.expect("Magnitude of the top bits should fit in a u32");
while sig >= OVERSIZE {
msd += 1;
sig >>= 1;
}
(sig & SIG_BITS, (126 + msd) as u32)
}
}
impl From<Real> for f32 {
fn from(r: Real) -> f32 {
use num::bigint::Sign::*;
const NEG_BITS: u32 = 0x8000_0000;
const EXP_BITS: u32 = 0x7f80_0000;
const SIG_BITS: u32 = 0x007f_ffff;
debug_assert_eq!(NEG_BITS + EXP_BITS + SIG_BITS, u32::MAX);
let c = r.fold();
let neg = match c.sign() {
NoSign => {
return 0.0;
}
Plus => 0,
Minus => 1,
};
let Some(msd) = c.iter_msd_stop(-150) else {
return match neg {
0 => 0.0,
1 => -0.0,
_ => unreachable!(),
};
};
if msd > 127 {
return match neg {
0 => f32::INFINITY,
1 => f32::NEG_INFINITY,
_ => unreachable!(),
};
}
let (sig_bits, exp) = sig_exp_32(c, msd);
let neg_bits: u32 = neg << NEG_BITS.trailing_zeros();
let exp_bits: u32 = exp << EXP_BITS.trailing_zeros();
let bits = neg_bits | exp_bits | sig_bits;
f32::from_bits(bits)
}
}
impl Real {
#[inline]
pub fn to_f32_lossy(&self) -> Option<f32> {
#[cfg(feature = "cached-f32-approx")]
if let PrimitiveApproxCache::F32(value) = self.primitive_approx_cache.get() {
return value;
}
let value = self.to_f32_lossy_uncached();
#[cfg(feature = "cached-f32-approx")]
if matches!(
self.primitive_approx_cache.get(),
PrimitiveApproxCache::Empty
) {
self.primitive_approx_cache
.set(PrimitiveApproxCache::F32(value));
}
value
}
fn to_f32_lossy_uncached(&self) -> Option<f32> {
const NEG_BITS: u32 = 0x8000_0000;
const EXP_BITS: u32 = 0x7f80_0000;
let c = self.fold_ref();
let sign = match self.refine_sign_until(-150) {
Some(sign) => sign,
None => return Some(0.0),
};
let neg = match sign {
RealSign::Zero => return Some(0.0),
RealSign::Positive => 0,
RealSign::Negative => 1,
};
let Some(msd) = c.iter_msd_stop(-150) else {
return Some(match neg {
0 => 0.0,
1 => -0.0,
_ => unreachable!(),
});
};
if msd > 127 {
return None;
}
let (sig_bits, exp) = sig_exp_32(c, msd);
let neg_bits: u32 = neg << NEG_BITS.trailing_zeros();
let exp_bits: u32 = exp << EXP_BITS.trailing_zeros();
let bits = neg_bits | exp_bits | sig_bits;
let value = f32::from_bits(bits);
value.is_finite().then_some(value)
}
}
fn sig_exp_64(c: Computable, mut msd: Precision) -> (u64, u64) {
const SIG_BITS: u64 = 0x000f_ffff_ffff_ffff;
const OVERSIZE: u64 = SIG_BITS.next_power_of_two() << 1;
if msd <= -1022 {
let sig = c
.approx(-1074)
.magnitude()
.try_into()
.expect("Magnitude of the top bits should fit in a u64");
if sig > SIG_BITS {
(sig & SIG_BITS, 1)
} else {
(sig, 0)
}
} else {
let mut sig: u64 = c
.approx(msd - 53)
.magnitude()
.try_into()
.expect("Magnitude of the top bits should fit in a u64");
while sig >= OVERSIZE {
msd += 1;
sig >>= 1;
}
(sig & SIG_BITS, (1022 + msd) as u64)
}
}
impl From<Real> for f64 {
fn from(r: Real) -> f64 {
use num::bigint::Sign::*;
const NEG_BITS: u64 = 0x8000_0000_0000_0000;
const EXP_BITS: u64 = 0x7ff0_0000_0000_0000;
const SIG_BITS: u64 = 0x000f_ffff_ffff_ffff;
debug_assert_eq!(NEG_BITS + EXP_BITS + SIG_BITS, u64::MAX);
let c = r.fold();
let neg = match c.sign() {
NoSign => {
return 0.0;
}
Plus => 0,
Minus => 1,
};
let Some(msd) = c.iter_msd_stop(-1075) else {
return match neg {
0 => 0.0,
1 => -0.0,
_ => unreachable!(),
};
};
if msd > 1023 {
return match neg {
0 => f64::INFINITY,
1 => f64::NEG_INFINITY,
_ => unreachable!(),
};
}
let (sig_bits, exp) = sig_exp_64(c, msd);
let neg_bits: u64 = neg << NEG_BITS.trailing_zeros();
let exp_bits: u64 = exp << EXP_BITS.trailing_zeros();
let bits = neg_bits | exp_bits | sig_bits;
f64::from_bits(bits)
}
}
impl Real {
#[inline]
pub fn to_f64_lossy(&self) -> Option<f64> {
#[cfg(feature = "cached-f64-approx")]
if let PrimitiveApproxCache::F64(value) = self.primitive_approx_cache.get() {
return value;
}
let value = self.to_f64_lossy_uncached();
#[cfg(feature = "cached-f64-approx")]
self.primitive_approx_cache
.set(PrimitiveApproxCache::F64(value));
value
}
fn to_f64_lossy_uncached(&self) -> Option<f64> {
const NEG_BITS: u64 = 0x8000_0000_0000_0000;
const EXP_BITS: u64 = 0x7ff0_0000_0000_0000;
use crate::real::Class;
if matches!(self.class, Class::One)
&& self.computable.is_none()
&& self.signal.is_none()
&& let fast @ Some(_) = self.rational.to_f64_lossy()
{
return fast;
}
if self.rational.is_one() {
let value = match &self.class {
Class::Pi => Some(std::f64::consts::PI),
Class::PiInv => Some(1.0 / std::f64::consts::PI),
Class::PiPow(power) => Some(std::f64::consts::PI.powi(i32::from(*power))),
Class::Sqrt(radicand) => radicand.to_f64_lossy().map(f64::sqrt),
_ => None,
};
if let Some(value) = value
&& value.is_finite()
{
return Some(value);
}
} else if self.rational.is_two() && matches!(self.class, Class::Pi) {
return Some(std::f64::consts::TAU);
}
if let Some(scale) = self.rational.to_f64_lossy() {
let value = match &self.class {
Class::Pi => Some(scale * std::f64::consts::PI),
Class::PiInv => Some(scale / std::f64::consts::PI),
Class::PiPow(power) => Some(scale * std::f64::consts::PI.powi(i32::from(*power))),
Class::Exp(exp) => exp.to_f64_lossy().map(|exp| scale * exp.exp()),
Class::PiExp(exp) => exp
.to_f64_lossy()
.map(|exp| scale * std::f64::consts::PI * exp.exp()),
Class::PiInvExp(exp) => exp
.to_f64_lossy()
.map(|exp| scale / std::f64::consts::PI * exp.exp()),
Class::Sqrt(radicand) => radicand
.to_f64_lossy()
.map(|radicand| scale * radicand.sqrt()),
Class::PiSqrt(radicand) => radicand
.to_f64_lossy()
.map(|radicand| scale * std::f64::consts::PI * radicand.sqrt()),
_ => None,
};
if let Some(value) = value
&& value.is_finite()
{
return Some(value);
}
}
let c = self.fold_ref();
let sign = match self.refine_sign_until(-1075) {
Some(sign) => sign,
None => return Some(0.0),
};
let neg = match sign {
RealSign::Zero => return Some(0.0),
RealSign::Positive => 0,
RealSign::Negative => 1,
};
let Some(msd) = c.iter_msd_stop(-1075) else {
return Some(0.0);
};
if msd > 1023 {
return None;
}
let (sig_bits, exp) = sig_exp_64(c, msd);
let neg_bits: u64 = neg << NEG_BITS.trailing_zeros();
let exp_bits: u64 = exp << EXP_BITS.trailing_zeros();
let bits = neg_bits | exp_bits | sig_bits;
let value = f64::from_bits(bits);
value.is_finite().then_some(value)
}
}
#[cfg(test)]
mod tests {
use num::bigint::ToBigInt;
use num::{BigInt, One};
#[cfg(any(feature = "cached-f32-approx", feature = "cached-f64-approx"))]
use proptest::prelude::*;
use super::*;
#[cfg(any(feature = "cached-f32-approx", feature = "cached-f64-approx"))]
fn finite_rational_strategy() -> impl Strategy<Value = Rational> {
(-1_000_000_i64..=1_000_000, 1_u64..=1_000_000).prop_map(|(numerator, denominator)| {
Rational::fraction(numerator, denominator).unwrap()
})
}
#[cfg(feature = "cached-f64-approx")]
fn nonzero_finite_rational_strategy() -> impl Strategy<Value = Rational> {
finite_rational_strategy().prop_filter("nonzero rational", |r| !r.is_zero())
}
#[test]
fn zero() {
let f: f32 = 0.0;
let d: f64 = 0.0;
let a: Real = f.try_into().unwrap();
let b: Real = d.try_into().unwrap();
let zero = Real::zero();
assert_eq!(a, zero);
assert_eq!(b, zero);
}
#[test]
fn infinity() {
let f = f32::INFINITY;
let d = f64::NEG_INFINITY;
let a: Problem = <f32 as TryInto<Real>>::try_into(f).unwrap_err();
let b: Problem = <f64 as TryInto<Real>>::try_into(d).unwrap_err();
assert_eq!(a, Problem::Infinity);
assert_eq!(b, Problem::Infinity);
}
#[test]
fn nans() {
let f = f32::NAN;
let d = f64::NAN;
let a: Problem = <f32 as TryInto<Real>>::try_into(f).unwrap_err();
let b: Problem = <f64 as TryInto<Real>>::try_into(d).unwrap_err();
assert_eq!(a, Problem::NotANumber);
assert_eq!(b, Problem::NotANumber);
}
#[test]
fn half_to_float() {
let half = Real::new(Rational::fraction(1, 2).unwrap());
let f: f32 = half.clone().into();
let d: f64 = half.into();
assert_eq!(f, 0.5);
assert_eq!(d, 0.5);
}
#[test]
fn half_from_float() {
let half = 0.5_f32;
let correct = Real::new(Rational::fraction(1, 2).unwrap());
let answer: Real = half.try_into().unwrap();
assert_eq!(answer, correct);
let half = 0.5_f64;
let correct = Real::new(Rational::fraction(1, 2).unwrap());
let answer: Real = half.try_into().unwrap();
assert_eq!(answer, correct);
}
#[test]
fn negative_half() {
let half = Real::new(Rational::fraction(-1, 2).unwrap());
let f: f32 = half.clone().into();
let d: f64 = half.into();
assert_eq!(f, -0.5);
assert_eq!(d, -0.5);
}
#[test]
fn rational() {
let f: f32 = 27.0;
let d: f32 = 81.0;
let a: Real = f.try_into().unwrap();
let b: Real = d.try_into().unwrap();
let third = Real::new(Rational::fraction(1, 3).unwrap());
let answer = (a / b).unwrap();
assert_eq!(answer, third);
}
#[test]
fn too_small() {
let r: Real = f32::from_bits(1).try_into().unwrap();
let s = r * Real::new(Rational::fraction(1, 3).unwrap());
let f: f32 = s.into();
assert_eq!(f, 0.0_f32);
let r: Real = f64::from_bits(1).try_into().unwrap();
let s = r * Real::new(Rational::fraction(1, 3).unwrap());
let f: f64 = s.into();
assert_eq!(f, 0.0_f64);
}
#[test]
fn repr_f32() {
let f: f32 = 1.234_567_9;
let a: Real = f.try_into().unwrap();
let correct = Real::new(Rational::fraction(5178153, 4194304).unwrap());
assert_eq!(a, correct);
}
#[test]
fn repr_f64() {
let f: f64 = 1.23456789;
let a: Real = f.try_into().unwrap();
let correct = Real::new(Rational::fraction(5559999489367579, 4503599627370496).unwrap());
assert_eq!(a, correct);
}
#[test]
fn fold_nine() {
let nine = Real::new(Rational::new(9));
let c = nine.fold();
assert_eq!(c.approx(3), BigInt::one());
let nine: BigInt = ToBigInt::to_bigint(&9).unwrap();
assert_eq!(c.approx(0), nine);
}
#[test]
fn zero_roundtrip() {
let zero = 0.0_f32;
let zero: Real = zero.try_into().unwrap();
assert_eq!(zero, Real::zero());
let zero: f32 = zero.into();
assert_eq!(zero, 0.0);
let zero = 0.0_f64;
let zero: Real = zero.try_into().unwrap();
assert_eq!(zero, Real::zero());
let zero: f64 = zero.into();
assert_eq!(zero, 0.0);
}
fn roundtrip<T>(f: T) -> T
where
T: TryInto<Real> + From<Real>,
<T as TryInto<Real>>::Error: std::fmt::Debug,
{
let mid: Real = f.try_into().unwrap();
mid.into()
}
#[test]
fn big_roundtrip() {
assert_eq!(f32::MAX, roundtrip(f32::MAX));
assert_eq!(f64::MAX, roundtrip(f64::MAX));
assert_eq!(f32::MIN, roundtrip(f32::MIN));
assert_eq!(f64::MIN, roundtrip(f64::MIN));
}
#[test]
fn small_roundtrip() {
assert_eq!(f32::MIN_POSITIVE * 3.0, roundtrip(f32::MIN_POSITIVE * 3.0));
assert_eq!(f64::MIN_POSITIVE * 3.0, roundtrip(f64::MIN_POSITIVE * 3.0));
assert_eq!(f32::MIN_POSITIVE, roundtrip(f32::MIN_POSITIVE));
assert_eq!(f64::MIN_POSITIVE, roundtrip(f64::MIN_POSITIVE));
}
#[test]
fn arbitrary_roundtrip() {
assert_eq!(0.123_456_79_f32, roundtrip(0.123_456_79_f32));
assert_eq!(987654321_f32, roundtrip(987654321_f32));
assert_eq!(0.123456789_f64, roundtrip(0.123456789_f64));
assert_eq!(987654321_f64, roundtrip(987654321_f64));
}
#[test]
fn almost_two() {
let h = f32::from_bits(0x3fff_ffff);
assert_eq!(format!("{h:#.7}"), "1.9999999");
let r: Real = h.try_into().unwrap();
assert_eq!(format!("{r:#.7}"), "1.9999999");
let j: f32 = r.into();
assert_eq!(h, j);
let h = f64::from_bits(0x3fff_ffff_ffff_ffff);
assert_eq!(format!("{h:#.16}"), "1.9999999999999998");
let r: Real = h.try_into().unwrap();
assert_eq!(format!("{r:#.16}"), "1.9999999999999998");
let j: f64 = r.into();
assert_eq!(h, j);
}
#[test]
fn subnormal_roundtrip() {
let before = 1.234e-310_f64;
assert_ne!(before, 0.0);
assert_eq!(before, roundtrip(before));
let before = 1.234e-41_f32;
assert_ne!(before, 0.0);
assert_eq!(before, roundtrip(before));
let sub = f32::from_bits(0x7c0000);
assert_eq!(sub, roundtrip(sub));
let sub = f64::from_bits(0x000f_ffff_0000_0000);
assert_eq!(sub, roundtrip(sub));
}
#[test]
fn bit_conversion() {
let r = Real::new(Rational::fraction(2, 5).unwrap()) * Real::pi();
let f: f32 = r.sin().into();
assert_eq!(f, 0.951_056_54);
}
#[test]
fn pi() {
let f: f32 = Real::pi().into();
assert!(std::f32::consts::PI.to_bits().abs_diff(f.to_bits()) < 2);
let f: f64 = Real::pi().into();
assert!(std::f64::consts::PI.to_bits().abs_diff(f.to_bits()) < 2);
}
#[test]
fn max_u64_f32() {
let max_u64: Rational = u64::MAX.into();
let r = Real::new(max_u64);
let f: f32 = r.into();
assert_eq!(f, u64::MAX as f32);
}
#[test]
fn max_u64_f64() {
let max_u64: Rational = u64::MAX.into();
let r = Real::new(max_u64);
let d: f64 = r.into();
assert_eq!(d, u64::MAX as f64);
}
#[test]
fn borrowed_f64_lossy_finite_values() {
let half = Real::new(Rational::fraction(1, 2).unwrap());
assert_eq!(half.to_f64_lossy(), Some(0.5));
let one_third = Real::new(Rational::fraction(1, 3).unwrap());
assert_eq!(one_third.to_f64_lossy(), Some(1.0 / 3.0));
let pi = Real::pi().to_f64_lossy().unwrap();
assert!(std::f64::consts::PI.to_bits().abs_diff(pi.to_bits()) < 2);
}
#[test]
fn borrowed_lossy_float_exports_are_repeatable() {
let values = [
Real::from(0),
Real::new(Rational::fraction(1, 7).unwrap()),
-Real::pi(),
Real::from(2).sqrt().unwrap(),
];
for value in values {
let first_f32 = value.to_f32_lossy().map(f32::to_bits);
let second_f32 = value.to_f32_lossy().map(f32::to_bits);
assert_eq!(first_f32, second_f32);
let first_f64 = value.to_f64_lossy().map(f64::to_bits);
let second_f64 = value.to_f64_lossy().map(f64::to_bits);
assert_eq!(first_f64, second_f64);
}
}
#[test]
fn borrowed_f64_lossy_underflow_and_overflow() {
let tiny = Real::new(
Rational::from_bigint_fraction(BigInt::from(1), num::BigUint::from(1_u8) << 1200)
.unwrap(),
);
assert_eq!(tiny.to_f64_lossy(), Some(0.0));
let negative_tiny = -tiny;
assert_eq!(negative_tiny.to_f64_lossy(), Some(0.0));
let huge = Real::new(Rational::from_bigint(BigInt::from(1_u8) << 1200));
assert_eq!(huge.to_f64_lossy(), None);
let negative_huge = -huge;
assert_eq!(negative_huge.to_f64_lossy(), None);
}
#[test]
fn borrowed_f64_lossy_tracks_negative_finite_values() {
let value = -(Real::new(Rational::new(2)).sqrt().unwrap());
let approx = value.to_f64_lossy().unwrap();
assert!(approx.is_sign_negative());
assert!((approx + std::f64::consts::SQRT_2).abs() < 1e-15);
}
#[test]
fn borrowed_f64_lossy_keeps_positive_translation_over_negative_symbolic_tail() {
let sqrt_two = Real::from(2).sqrt().unwrap();
let value = Real::from(10) + (Real::from(1) - sqrt_two);
let approx = value.to_f64_lossy().unwrap();
assert!(approx.is_sign_positive());
assert!((approx - (11.0 - std::f64::consts::SQRT_2)).abs() < 1e-12);
}
#[test]
#[cfg(all(feature = "cached-f32-approx", feature = "cached-f64-approx"))]
fn primitive_approx_cache_fills_and_upgrades() {
let value = Real::pi();
let f32_value = value.to_f32_lossy().unwrap();
assert!(std::f32::consts::PI.to_bits().abs_diff(f32_value.to_bits()) < 2);
assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F32(Some(_))
));
let f64_value = value.to_f64_lossy().unwrap();
assert!(std::f64::consts::PI.to_bits().abs_diff(f64_value.to_bits()) < 2);
assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F64(Some(_))
));
}
#[test]
#[cfg(feature = "cached-f64-approx")]
fn primitive_approx_cache_keeps_overflow_state() {
let huge = Real::new(Rational::from_bigint(BigInt::from(1_u8) << 1200));
assert_eq!(huge.to_f64_lossy(), None);
assert!(matches!(
huge.primitive_approx_cache.get(),
PrimitiveApproxCache::F64(None)
));
}
#[test]
#[cfg(feature = "cached-f32-approx")]
fn primitive_approx_cache_keeps_f32_overflow_state() {
let huge = Real::new(Rational::from_bigint(BigInt::from(1_u8) << 200));
assert_eq!(huge.to_f32_lossy(), None);
assert!(matches!(
huge.primitive_approx_cache.get(),
PrimitiveApproxCache::F32(None)
));
}
#[test]
#[cfg(feature = "cached-f64-approx")]
fn abort_invalidates_primitive_approx_cache() {
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
let mut value = Real::pi();
assert!(value.to_f64_lossy().is_some());
assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F64(Some(_))
));
value.abort(Arc::new(AtomicBool::new(false)));
assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::Empty
));
}
#[cfg(any(feature = "cached-f32-approx", feature = "cached-f64-approx"))]
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
#[cfg(feature = "cached-f64-approx")]
fn cached_f64_matches_uncached_for_rationals(rational in finite_rational_strategy()) {
let value = Real::new(rational.clone());
let expected = Real::new(rational).to_f64_lossy_uncached();
prop_assert_eq!(value.to_f64_lossy().map(f64::to_bits), expected.map(f64::to_bits));
prop_assert_eq!(value.to_f64_lossy().map(f64::to_bits), expected.map(f64::to_bits));
prop_assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F64(_)
));
}
#[test]
#[cfg(feature = "cached-f32-approx")]
fn cached_f32_matches_uncached_for_rationals(rational in finite_rational_strategy()) {
let value = Real::new(rational.clone());
let expected = Real::new(rational).to_f32_lossy_uncached();
prop_assert_eq!(value.to_f32_lossy().map(f32::to_bits), expected.map(f32::to_bits));
prop_assert_eq!(value.to_f32_lossy().map(f32::to_bits), expected.map(f32::to_bits));
prop_assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F32(_)
));
}
#[test]
#[cfg(all(feature = "cached-f32-approx", feature = "cached-f64-approx"))]
fn f32_then_f64_cache_upgrade_matches_uncached(rational in finite_rational_strategy()) {
let value = Real::new(rational.clone());
let expected_f32 = Real::new(rational.clone()).to_f32_lossy_uncached();
let expected_f64 = Real::new(rational).to_f64_lossy_uncached();
prop_assert_eq!(value.to_f32_lossy().map(f32::to_bits), expected_f32.map(f32::to_bits));
prop_assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F32(_)
));
prop_assert_eq!(value.to_f64_lossy().map(f64::to_bits), expected_f64.map(f64::to_bits));
prop_assert!(matches!(
value.primitive_approx_cache.get(),
PrimitiveApproxCache::F64(_)
));
}
#[test]
#[cfg(feature = "cached-f64-approx")]
fn cached_f64_is_not_reused_after_negation(rational in nonzero_finite_rational_strategy()) {
let value = Real::new(rational.clone());
let cached_positive = value.to_f64_lossy();
let negated_ref = -&value;
let expected_ref = Real::new(-rational.clone()).to_f64_lossy_uncached();
prop_assume!(cached_positive.map(f64::to_bits) != expected_ref.map(f64::to_bits));
prop_assert!(matches!(
negated_ref.primitive_approx_cache.get(),
PrimitiveApproxCache::Empty
));
prop_assert_eq!(negated_ref.to_f64_lossy().map(f64::to_bits), expected_ref.map(f64::to_bits));
let owned = Real::new(rational.clone());
let _ = owned.to_f64_lossy();
let negated_owned = -owned;
let expected_owned = Real::new(-rational).to_f64_lossy_uncached();
prop_assert!(matches!(
negated_owned.primitive_approx_cache.get(),
PrimitiveApproxCache::Empty
));
prop_assert_eq!(negated_owned.to_f64_lossy().map(f64::to_bits), expected_owned.map(f64::to_bits));
}
}
}