use crate::{
integer::{small::Mpz, ToSmall},
Assign, Rational,
};
use az::Az;
use core::{
cell::UnsafeCell,
mem::{self, MaybeUninit},
ops::Deref,
ptr,
};
use gmp_mpfr_sys::gmp::{self, limb_t, mpq_t};
const LIMBS_IN_SMALL: usize = (128 / gmp::LIMB_BITS) as usize;
type Limbs = [MaybeUninit<limb_t>; LIMBS_IN_SMALL];
pub struct SmallRational {
inner: Mpq,
first_limbs: Limbs,
last_limbs: Limbs,
}
unsafe impl Send for SmallRational {}
impl Clone for SmallRational {
#[inline]
fn clone(&self) -> SmallRational {
let (first_limbs, last_limbs) = if self.num_is_first() {
(&self.first_limbs, &self.last_limbs)
} else {
(&self.last_limbs, &self.first_limbs)
};
SmallRational {
inner: self.inner.clone(),
first_limbs: *first_limbs,
last_limbs: *last_limbs,
}
}
}
#[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 fn new() -> Self {
SmallRational {
inner: Mpq {
num: Mpz {
alloc: LIMBS_IN_SMALL.az(),
size: 0,
d: UnsafeCell::new(ptr::null_mut()),
},
den: Mpz {
alloc: LIMBS_IN_SMALL.az(),
size: 1,
d: UnsafeCell::new(ptr::null_mut()),
},
},
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 dst = SmallRational::default();
dst.assign_canonical(num, den);
dst
}
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() as usize) <= (*self.inner.den.d.get() as usize) }
}
#[inline]
fn update_d(&self) {
let first = self.first_limbs[0].as_ptr() as *mut limb_t;
let last = self.last_limbs[0].as_ptr() as *mut limb_t;
let (num_d, den_d) = if self.num_is_first() {
(first, last)
} else {
(last, first)
};
unsafe {
*self.inner.num.d.get() = num_d;
*self.inner.den.d.get() = den_d;
}
}
}
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 dst = SmallRational::default();
src.copy(&mut dst.inner.num.size, &mut dst.first_limbs);
dst.inner.den.size = 1;
dst.last_limbs[0] = MaybeUninit::new(1);
dst
}
}
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);
}
unsafe {
gmp::mpq_canonicalize(self.as_nonreallocating_rational().as_raw_mut());
}
}
}
impl<Num: ToSmall, Den: ToSmall> From<(Num, Den)> for SmallRational {
fn from(src: (Num, Den)) -> Self {
let mut dst = SmallRational::default();
dst.assign(src);
dst
}
}
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 as usize) > (den as usize)
}
}
#[test]
fn check_swapped_parts() {
let mut r = SmallRational::from((2, 3));
assert!(!swapped_parts(&r));
assert_eq!(*r.clone(), *r);
unsafe {
r.as_nonreallocating_rational().recip_mut();
}
assert!(swapped_parts(&r));
assert_eq!(*r, (3, 2));
assert_eq!(*r.clone(), *r);
unsafe {
r.assign_canonical(5, 7);
}
assert!(swapped_parts(&r));
assert_eq!(*r, (5, 7));
r.assign(2);
assert!(swapped_parts(&r));
assert_eq!(*r, 2);
r.assign((3, 5));
assert!(swapped_parts(&r));
assert_eq!(*r, (3, 5));
}
}