use crate::{
ext::xmpfr::{self, raw_round},
float::Round,
misc::{AsOrPanic, NegAbs},
Assign, Float,
};
use az::Az;
use core::{
cell::UnsafeCell,
mem::{self, MaybeUninit},
ops::Deref,
ptr,
};
use gmp_mpfr_sys::{
gmp::{self, limb_t},
mpfr::{self, exp_t, mpfr_t, prec_t},
};
use libc::c_int;
const LIMBS_IN_SMALL: usize = (128 / gmp::LIMB_BITS) as usize;
type Limbs = [MaybeUninit<limb_t>; LIMBS_IN_SMALL];
#[derive(Clone)]
pub struct SmallFloat {
inner: Mpfr,
limbs: Limbs,
}
#[repr(C)]
pub struct Mpfr {
pub prec: prec_t,
pub sign: c_int,
pub exp: exp_t,
pub d: UnsafeCell<*mut limb_t>,
}
static_assert!(mem::size_of::<Limbs>() == 16);
static_assert_same_layout!(Mpfr, mpfr_t);
impl Clone for Mpfr {
#[inline]
fn clone(&self) -> Mpfr {
Mpfr {
prec: self.prec,
sign: self.sign,
exp: self.exp,
d: UnsafeCell::new(ptr::null_mut()),
}
}
}
unsafe impl Send for SmallFloat {}
impl SmallFloat {
#[inline]
pub unsafe fn as_nonreallocating_float(&mut self) -> &mut Float {
self.update_d();
let ptr = cast_ptr_mut!(&mut self.inner, Float);
&mut *ptr
}
#[inline]
fn update_d(&self) {
let d = self.limbs[0].as_ptr() as *mut limb_t;
unsafe {
*self.inner.d.get() = d;
}
}
}
impl Deref for SmallFloat {
type Target = Float;
#[inline]
fn deref(&self) -> &Float {
self.update_d();
let ptr = cast_ptr!(&self.inner, Float);
unsafe { &*ptr }
}
}
pub trait ToSmall: SealedToSmall {}
pub trait SealedToSmall: Copy {
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs);
}
macro_rules! signed {
($($I:ty)*) => { $(
impl ToSmall for $I {}
impl SealedToSmall for $I {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
let (neg, abs) = self.neg_abs();
abs.copy(inner, limbs);
if neg {
(*inner).sign = -1;
}
}
}
)* };
}
macro_rules! unsigned_32 {
($U:ty, $bits:expr) => {
impl ToSmall for $U {}
impl SealedToSmall for $U {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
let ptr = cast_ptr_mut!(inner, mpfr_t);
let limbs_ptr = limbs[0].as_mut_ptr();
if self == 0 {
xmpfr::custom_zero(ptr, limbs_ptr, $bits);
} else {
let leading = self.leading_zeros();
let limb_leading = leading + gmp::LIMB_BITS.az::<u32>() - $bits;
limbs[0] = MaybeUninit::new(limb_t::from(self) << limb_leading);
let exp = $bits - leading;
xmpfr::custom_regular(ptr, limbs_ptr, exp.as_or_panic(), $bits);
}
}
}
};
}
signed! { i8 i16 i32 i64 i128 isize }
unsigned_32! { u8, 8 }
unsigned_32! { u16, 16 }
unsigned_32! { u32, 32 }
impl ToSmall for u64 {}
impl SealedToSmall for u64 {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
let ptr = cast_ptr_mut!(inner, mpfr_t);
let limbs_ptr = limbs[0].as_mut_ptr();
if self == 0 {
xmpfr::custom_zero(ptr, limbs_ptr, 64);
} else {
let leading = self.leading_zeros();
let sval = self << leading;
#[cfg(gmp_limb_bits_64)]
{
limbs[0] = MaybeUninit::new(sval);
}
#[cfg(gmp_limb_bits_32)]
{
limbs[0] = MaybeUninit::new(sval as u32);
limbs[1] = MaybeUninit::new((sval >> 32) as u32);
}
xmpfr::custom_regular(ptr, limbs_ptr, (64 - leading).as_or_panic(), 64);
}
}
}
impl ToSmall for u128 {}
impl SealedToSmall for u128 {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
let ptr = cast_ptr_mut!(inner, mpfr_t);
let limbs_ptr = limbs[0].as_mut_ptr();
if self == 0 {
xmpfr::custom_zero(ptr, limbs_ptr, 128);
} else {
let leading = self.leading_zeros();
let sval = self << leading;
#[cfg(gmp_limb_bits_64)]
{
limbs[0] = MaybeUninit::new(sval as u64);
limbs[1] = MaybeUninit::new((sval >> 64) as u64);
}
#[cfg(gmp_limb_bits_32)]
{
limbs[0] = MaybeUninit::new(sval as u32);
limbs[1] = MaybeUninit::new((sval >> 32) as u32);
limbs[2] = MaybeUninit::new((sval >> 64) as u32);
limbs[3] = MaybeUninit::new((sval >> 96) as u32);
}
xmpfr::custom_regular(ptr, limbs_ptr, (128 - leading).as_or_panic(), 128);
}
}
}
impl ToSmall for usize {}
impl SealedToSmall for usize {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
#[cfg(target_pointer_width = "32")]
{
(self as u32).copy(inner, limbs);
}
#[cfg(target_pointer_width = "64")]
{
(self as u64).copy(inner, limbs);
}
}
}
impl ToSmall for f32 {}
impl SealedToSmall for f32 {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
let ptr = cast_ptr_mut!(inner, mpfr_t);
let limbs_ptr = limbs[0].as_mut_ptr();
xmpfr::custom_zero(ptr, limbs_ptr, 24);
mpfr::set_d(ptr, self.into(), raw_round(Round::Nearest));
if self.is_sign_negative() {
(*inner).sign = -1;
}
}
}
impl ToSmall for f64 {}
impl SealedToSmall for f64 {
#[inline]
unsafe fn copy(self, inner: *mut Mpfr, limbs: &mut Limbs) {
let ptr = cast_ptr_mut!(inner, mpfr_t);
let limbs_ptr = limbs[0].as_mut_ptr();
xmpfr::custom_zero(ptr, limbs_ptr, 53);
mpfr::set_d(ptr, self, raw_round(Round::Nearest));
if self.is_sign_negative() {
(*inner).sign = -1;
}
}
}
impl<T: ToSmall> Assign<T> for SmallFloat {
#[inline]
fn assign(&mut self, src: T) {
unsafe {
src.copy(&mut self.inner, &mut self.limbs);
}
}
}
impl<T: ToSmall> From<T> for SmallFloat {
#[inline]
fn from(src: T) -> Self {
let mut dst = SmallFloat {
inner: Mpfr {
prec: 0,
sign: 0,
exp: 0,
d: UnsafeCell::new(ptr::null_mut()),
},
limbs: small_limbs![],
};
unsafe {
src.copy(&mut dst.inner, &mut dst.limbs);
}
dst
}
}
impl Assign<&Self> for SmallFloat {
#[inline]
fn assign(&mut self, other: &Self) {
self.clone_from(other);
}
}
impl Assign for SmallFloat {
#[inline]
fn assign(&mut self, other: Self) {
drop(mem::replace(self, other));
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use crate::{
float::{self, FreeCache, SmallFloat},
Assign,
};
#[test]
fn check_assign() {
let mut f = SmallFloat::from(-1.0f32);
assert_eq!(*f, -1.0);
f.assign(-2.0f64);
assert_eq!(*f, -2.0);
let other = SmallFloat::from(4u8);
f.assign(&other);
assert_eq!(*f, 4);
f.assign(5i8);
assert_eq!(*f, 5);
f.assign(other);
assert_eq!(*f, 4);
f.assign(6u16);
assert_eq!(*f, 6);
f.assign(-6i16);
assert_eq!(*f, -6);
f.assign(6u32);
assert_eq!(*f, 6);
f.assign(-6i32);
assert_eq!(*f, -6);
f.assign(6u64);
assert_eq!(*f, 6);
f.assign(-6i64);
assert_eq!(*f, -6);
f.assign(6u128);
assert_eq!(*f, 6);
f.assign(-6i128);
assert_eq!(*f, -6);
f.assign(6usize);
assert_eq!(*f, 6);
f.assign(-6isize);
assert_eq!(*f, -6);
f.assign(0u32);
assert_eq!(*f, 0);
float::free_cache(FreeCache::All);
}
}