use crate::{
ext::xmpq,
integer::{small::Mpz, ToSmall},
Assign, Rational,
};
use az::Cast;
use core::{
cell::UnsafeCell,
mem::{self, MaybeUninit},
ops::Deref,
ptr::NonNull,
};
use gmp_mpfr_sys::gmp::{self, limb_t, mpq_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 SmallRational {
inner: Mpq,
first_limbs: Limbs,
last_limbs: Limbs,
}
unsafe impl Send for SmallRational {}
#[derive(Clone)]
#[repr(C)]
struct Mpq {
num: Mpz,
den: Mpz,
}
static_assert_same_layout!(Mpq, mpq_t);
impl Default for SmallRational {
#[inline]
fn default() -> Self {
SmallRational::new()
}
}
impl SmallRational {
#[inline]
pub const fn new() -> Self {
SmallRational {
inner: Mpq {
num: Mpz {
alloc: LIMBS_IN_SMALL as c_int,
size: 0,
d: UnsafeCell::new(NonNull::dangling()),
},
den: Mpz {
alloc: LIMBS_IN_SMALL as c_int,
size: 1,
d: UnsafeCell::new(NonNull::dangling()),
},
},
first_limbs: small_limbs![0],
last_limbs: small_limbs![1],
}
}
#[inline]
pub unsafe fn as_nonreallocating_rational(&mut self) -> &mut Rational {
self.update_d();
let ptr = cast_ptr_mut!(&mut self.inner, Rational);
&mut *ptr
}
pub unsafe fn from_canonical<Num: ToSmall, Den: ToSmall>(num: Num, den: Den) -> Self {
let mut num_size = 0;
let mut den_size = 0;
let mut num_limbs: Limbs = small_limbs![0];
let mut den_limbs: Limbs = small_limbs![0];
num.copy(&mut num_size, &mut num_limbs);
den.copy(&mut den_size, &mut den_limbs);
SmallRational {
inner: Mpq {
num: Mpz {
alloc: LIMBS_IN_SMALL.cast(),
size: num_size,
d: UnsafeCell::new(NonNull::dangling()),
},
den: Mpz {
alloc: LIMBS_IN_SMALL.cast(),
size: den_size,
d: UnsafeCell::new(NonNull::dangling()),
},
},
first_limbs: num_limbs,
last_limbs: den_limbs,
}
}
pub unsafe fn assign_canonical<Num: ToSmall, Den: ToSmall>(&mut self, num: Num, den: Den) {
let (num_limbs, den_limbs) = if self.num_is_first() {
(&mut self.first_limbs, &mut self.last_limbs)
} else {
(&mut self.last_limbs, &mut self.first_limbs)
};
num.copy(&mut self.inner.num.size, num_limbs);
den.copy(&mut self.inner.den.size, den_limbs);
}
#[inline]
fn num_is_first(&self) -> bool {
unsafe { *self.inner.num.d.get() <= *self.inner.den.d.get() }
}
#[inline]
fn update_d(&self) {
let first = NonNull::<[MaybeUninit<limb_t>]>::from(&self.first_limbs[..]);
let last = NonNull::<[MaybeUninit<limb_t>]>::from(&self.last_limbs[..]);
let (num_d, den_d) = if self.num_is_first() {
(first, last)
} else {
(last, first)
};
unsafe {
*self.inner.num.d.get() = num_d.cast();
*self.inner.den.d.get() = den_d.cast();
}
}
}
impl Deref for SmallRational {
type Target = Rational;
#[inline]
fn deref(&self) -> &Rational {
self.update_d();
let ptr = cast_ptr!(&self.inner, Rational);
unsafe { &*ptr }
}
}
impl<Num: ToSmall> Assign<Num> for SmallRational {
#[inline]
fn assign(&mut self, src: Num) {
let (num_limbs, den_limbs) = if self.num_is_first() {
(&mut self.first_limbs, &mut self.last_limbs)
} else {
(&mut self.last_limbs, &mut self.first_limbs)
};
src.copy(&mut self.inner.num.size, num_limbs);
self.inner.den.size = 1;
den_limbs[0] = MaybeUninit::new(1);
}
}
impl<Num: ToSmall> From<Num> for SmallRational {
fn from(src: Num) -> Self {
let mut num_size = 0;
let mut num_limbs = small_limbs![0];
src.copy(&mut num_size, &mut num_limbs);
SmallRational {
inner: Mpq {
num: Mpz {
alloc: LIMBS_IN_SMALL.cast(),
size: num_size,
d: UnsafeCell::new(NonNull::dangling()),
},
den: Mpz {
alloc: LIMBS_IN_SMALL.cast(),
size: 1,
d: UnsafeCell::new(NonNull::dangling()),
},
},
first_limbs: num_limbs,
last_limbs: small_limbs![1],
}
}
}
impl<Num: ToSmall, Den: ToSmall> Assign<(Num, Den)> for SmallRational {
fn assign(&mut self, src: (Num, Den)) {
assert!(!src.1.is_zero(), "division by zero");
{
let (num_limbs, den_limbs) = if self.num_is_first() {
(&mut self.first_limbs, &mut self.last_limbs)
} else {
(&mut self.last_limbs, &mut self.first_limbs)
};
src.0.copy(&mut self.inner.num.size, num_limbs);
src.1.copy(&mut self.inner.den.size, den_limbs);
}
xmpq::canonicalize(unsafe { self.as_nonreallocating_rational() });
}
}
impl<Num: ToSmall, Den: ToSmall> From<(Num, Den)> for SmallRational {
fn from(src: (Num, Den)) -> Self {
assert!(!src.1.is_zero(), "division by zero");
let mut inner = Mpq {
num: Mpz {
alloc: LIMBS_IN_SMALL.cast(),
size: 0,
d: UnsafeCell::new(NonNull::dangling()),
},
den: Mpz {
alloc: LIMBS_IN_SMALL.cast(),
size: 0,
d: UnsafeCell::new(NonNull::dangling()),
},
};
let mut num_limbs: Limbs = small_limbs![0];
let mut den_limbs: Limbs = small_limbs![0];
src.0.copy(&mut inner.num.size, &mut num_limbs);
src.1.copy(&mut inner.den.size, &mut den_limbs);
inner.num.d =
UnsafeCell::new(NonNull::<[MaybeUninit<limb_t>]>::from(&mut num_limbs[..]).cast());
inner.den.d =
UnsafeCell::new(NonNull::<[MaybeUninit<limb_t>]>::from(&mut den_limbs[..]).cast());
unsafe {
gmp::mpq_canonicalize(cast_ptr_mut!(&mut inner, mpq_t));
}
if num_limbs.as_ptr() <= den_limbs.as_ptr() {
SmallRational {
inner,
first_limbs: num_limbs,
last_limbs: den_limbs,
}
} else {
SmallRational {
inner,
first_limbs: den_limbs,
last_limbs: num_limbs,
}
}
}
}
impl Assign<&Self> for SmallRational {
#[inline]
fn assign(&mut self, other: &Self) {
self.clone_from(other);
}
}
impl Assign for SmallRational {
#[inline]
fn assign(&mut self, other: Self) {
drop(mem::replace(self, other));
}
}
#[cfg(test)]
mod tests {
use crate::{rational::SmallRational, Assign};
#[test]
fn check_assign() {
let mut r = SmallRational::from((1, 2));
assert_eq!(*r, (1, 2));
r.assign(3);
assert_eq!(*r, 3);
let other = SmallRational::from((4, 5));
r.assign(&other);
assert_eq!(*r, (4, 5));
r.assign((6, 7));
assert_eq!(*r, (6, 7));
r.assign(other);
assert_eq!(*r, (4, 5));
}
fn swapped_parts(small: &SmallRational) -> bool {
unsafe {
let num = (*small.numer().as_raw()).d;
let den = (*small.denom().as_raw()).d;
num > den
}
}
#[test]
fn check_swapped_parts() {
let mut r = SmallRational::from((2, 3));
assert_eq!(*r, (2, 3));
assert_eq!(*r.clone(), *r);
let mut orig_swapped_parts = swapped_parts(&r);
unsafe {
r.as_nonreallocating_rational().recip_mut();
}
assert_eq!(*r, (3, 2));
assert_eq!(*r.clone(), *r);
assert!(swapped_parts(&r) != orig_swapped_parts);
unsafe {
r.assign_canonical(5, 7);
}
assert_eq!(*r, (5, 7));
assert_eq!(*r.clone(), *r);
orig_swapped_parts = swapped_parts(&r);
unsafe {
r.as_nonreallocating_rational().recip_mut();
}
assert_eq!(*r, (7, 5));
assert_eq!(*r.clone(), *r);
assert!(swapped_parts(&r) != orig_swapped_parts);
r.assign(2);
assert_eq!(*r, 2);
assert_eq!(*r.clone(), *r);
orig_swapped_parts = swapped_parts(&r);
unsafe {
r.as_nonreallocating_rational().recip_mut();
}
assert_eq!(*r, (1, 2));
assert_eq!(*r.clone(), *r);
assert!(swapped_parts(&r) != orig_swapped_parts);
r.assign((3, -5));
assert_eq!(*r, (-3, 5));
assert_eq!(*r.clone(), *r);
orig_swapped_parts = swapped_parts(&r);
unsafe {
r.as_nonreallocating_rational().recip_mut();
}
assert_eq!(*r, (-5, 3));
assert_eq!(*r.clone(), *r);
assert!(swapped_parts(&r) != orig_swapped_parts);
}
}