use super::ffi;
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
pub struct FlintInteger {
inner: ffi::fmpz,
}
unsafe impl Send for FlintInteger {}
unsafe impl Sync for FlintInteger {}
impl FlintInteger {
pub fn new() -> Self {
let mut inner: ffi::fmpz = 0;
crate::flint::note_thread_uses_flint();
unsafe { ffi::fmpz_init(&mut inner) };
FlintInteger { inner }
}
pub fn from_i64(val: i64) -> Self {
let mut f = Self::new();
unsafe { ffi::fmpz_set_si(&mut f.inner, val) };
f
}
pub fn to_i64(&self) -> i64 {
unsafe { ffi::fmpz_get_si(&self.inner) }
}
pub fn gcd(&self, other: &Self) -> Self {
let mut res = Self::new();
unsafe { ffi::fmpz_gcd(&mut res.inner, &self.inner, &other.inner) };
res
}
pub fn is_zero(&self) -> bool {
self.inner == 0
}
pub fn checked_div(&self, rhs: &Self) -> Option<Self> {
(!rhs.is_zero()).then(|| self / rhs)
}
pub fn checked_rem(&self, rhs: &Self) -> Option<Self> {
(!rhs.is_zero()).then(|| self % rhs)
}
pub fn pow(&self, exp: u64) -> Self {
let mut res = Self::new();
unsafe { ffi::fmpz_pow_ui(&mut res.inner, &self.inner, exp) };
res
}
pub fn from_rug(n: &rug::Integer) -> Self {
let mut f = Self::new();
unsafe { fmpz_set_rug(&mut f.inner, n) };
f
}
pub(crate) fn inner_ptr(&self) -> *const ffi::fmpz {
&self.inner
}
pub(crate) fn inner_mut_ptr(&mut self) -> *mut ffi::fmpz {
&mut self.inner
}
pub fn to_rug(&self) -> rug::Integer {
unsafe { fmpz_get_rug(&self.inner) }
}
}
fn elem_size<T>(_: &[T]) -> usize {
std::mem::size_of::<T>()
}
pub(crate) unsafe fn fmpz_set_rug(f: *mut ffi::fmpz, n: &rug::Integer) {
if let Some(v) = n.to_i64() {
ffi::fmpz_set_si(f, v);
return;
}
let limbs = n.as_limbs();
if elem_size(limbs) == std::mem::size_of::<ffi::ulong>() {
ffi::fmpz_set_ui_array(
f,
limbs.as_ptr().cast::<ffi::ulong>(),
limbs.len() as ffi::slong,
);
} else {
let words = n.to_digits::<u64>(rug::integer::Order::Lsf);
ffi::fmpz_set_ui_array(f, words.as_ptr(), words.len() as ffi::slong);
}
if n.cmp0() == std::cmp::Ordering::Less {
ffi::fmpz_neg(f, f);
}
}
pub(crate) unsafe fn fmpz_get_rug(f: *const ffi::fmpz) -> rug::Integer {
if ffi::fmpz_fits_si(f) != 0 {
return rug::Integer::from(ffi::fmpz_get_si(f));
}
let negative = ffi::fmpz_cmp_si(f, 0) < 0;
let mut abs = FlintInteger::new();
ffi::fmpz_abs(&mut abs.inner, f);
let words = fmpz_abs_words(&abs.inner);
let r = rug::Integer::from_digits(&words, rug::integer::Order::Lsf);
if negative {
-r
} else {
r
}
}
fn words_for_bits(bits: u64) -> usize {
bits.div_ceil(64) as usize
}
unsafe fn fmpz_abs_words(abs: *const ffi::fmpz) -> Vec<u64> {
let len = words_for_bits(ffi::fmpz_bits(abs));
let mut words = vec![0u64; len];
ffi::fmpz_get_ui_array(words.as_mut_ptr(), len as ffi::slong, abs);
words
}
impl Default for FlintInteger {
fn default() -> Self {
Self::new()
}
}
impl Drop for FlintInteger {
fn drop(&mut self) {
unsafe { ffi::fmpz_clear(&mut self.inner) };
}
}
impl Clone for FlintInteger {
fn clone(&self) -> Self {
let mut new = Self::new();
unsafe { ffi::fmpz_set(&mut new.inner, &self.inner) };
new
}
}
impl PartialEq for FlintInteger {
fn eq(&self, other: &Self) -> bool {
unsafe { ffi::fmpz_equal(&self.inner, &other.inner) != 0 }
}
}
impl Eq for FlintInteger {}
impl Add for FlintInteger {
type Output = Self;
fn add(self, rhs: Self) -> Self {
&self + &rhs
}
}
impl<'b> Add<&'b FlintInteger> for &FlintInteger {
type Output = FlintInteger;
fn add(self, rhs: &'b FlintInteger) -> FlintInteger {
let mut res = FlintInteger::new();
unsafe { ffi::fmpz_add(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl Sub for FlintInteger {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
&self - &rhs
}
}
impl<'b> Sub<&'b FlintInteger> for &FlintInteger {
type Output = FlintInteger;
fn sub(self, rhs: &'b FlintInteger) -> FlintInteger {
let mut res = FlintInteger::new();
unsafe { ffi::fmpz_sub(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl Mul for FlintInteger {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
&self * &rhs
}
}
impl<'b> Mul<&'b FlintInteger> for &FlintInteger {
type Output = FlintInteger;
fn mul(self, rhs: &'b FlintInteger) -> FlintInteger {
let mut res = FlintInteger::new();
unsafe { ffi::fmpz_mul(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl Div for FlintInteger {
type Output = Self;
fn div(self, rhs: Self) -> Self {
&self / &rhs
}
}
impl<'b> Div<&'b FlintInteger> for &FlintInteger {
type Output = FlintInteger;
fn div(self, rhs: &'b FlintInteger) -> FlintInteger {
assert!(!rhs.is_zero(), "attempt to divide a FlintInteger by zero");
let mut res = FlintInteger::new();
unsafe { ffi::fmpz_tdiv_q(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl Rem for FlintInteger {
type Output = Self;
fn rem(self, rhs: Self) -> Self {
&self % &rhs
}
}
impl<'b> Rem<&'b FlintInteger> for &FlintInteger {
type Output = FlintInteger;
fn rem(self, rhs: &'b FlintInteger) -> FlintInteger {
assert!(
!rhs.is_zero(),
"attempt to calculate the remainder of a FlintInteger with a divisor of zero"
);
let mut q = FlintInteger::new();
let mut r = FlintInteger::new();
unsafe { ffi::fmpz_tdiv_qr(&mut q.inner, &mut r.inner, &self.inner, &rhs.inner) };
r
}
}
impl Neg for FlintInteger {
type Output = Self;
fn neg(self) -> Self {
-&self
}
}
impl Neg for &FlintInteger {
type Output = FlintInteger;
fn neg(self) -> FlintInteger {
let mut res = FlintInteger::new();
unsafe { ffi::fmpz_neg(&mut res.inner, &self.inner) };
res
}
}
impl fmt::Display for FlintInteger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
unsafe {
let ptr = ffi::fmpz_get_str(std::ptr::null_mut(), 10, &self.inner);
if ptr.is_null() {
return write!(f, "<err>");
}
let s = std::ffi::CStr::from_ptr(ptr)
.to_str()
.unwrap_or("<utf8-err>")
.to_owned();
ffi::flint_free(ptr as *mut std::ffi::c_void);
write!(f, "{}", s)
}
}
}
impl fmt::Debug for FlintInteger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FlintInteger({})", self)
}
}
pub(crate) struct FlintIntFactor {
inner: ffi::FmpzFactorStruct,
}
impl FlintIntFactor {
pub fn new() -> Self {
let mut inner = std::mem::MaybeUninit::<ffi::FmpzFactorStruct>::uninit();
crate::flint::note_thread_uses_flint();
unsafe { ffi::fmpz_factor_init(inner.as_mut_ptr()) };
Self {
inner: unsafe { inner.assume_init() },
}
}
pub fn factor(&mut self, n: &FlintInteger) {
unsafe { ffi::fmpz_factor(&mut self.inner, n.inner_ptr()) };
}
pub(crate) fn factor_smooth(&mut self, n: &FlintInteger, bits: i64, proved: bool) -> bool {
unsafe {
ffi::fmpz_factor_smooth(
&mut self.inner,
n.inner_ptr(),
bits as ffi::slong,
i32::from(proved) as std::os::raw::c_int,
) != 0
}
}
pub fn sign(&self) -> i32 {
self.inner.sign
}
pub fn len(&self) -> usize {
self.inner.num.max(0) as usize
}
pub fn base_at(&self, i: usize) -> FlintInteger {
assert!(
i < self.len(),
"factor index {i} out of range (len {})",
self.len()
);
let mut f = FlintInteger::new();
unsafe { ffi::fmpz_set(f.inner_mut_ptr(), self.inner.p.add(i)) };
f
}
pub fn exp_at(&self, i: usize) -> u64 {
assert!(
i < self.len(),
"factor index {i} out of range (len {})",
self.len()
);
unsafe { *self.inner.exp.add(i) }
}
}
impl Drop for FlintIntFactor {
fn drop(&mut self) {
unsafe { ffi::fmpz_factor_clear(&mut self.inner) };
}
}
#[cfg(kani)]
mod verification {
use super::*;
mod models {
use super::ffi;
pub unsafe fn fmpz_bits(f: *const ffi::fmpz) -> ffi::ulong {
*f as ffi::ulong
}
pub unsafe fn fmpz_get_ui_array(
out: *mut ffi::ulong,
n: ffi::slong,
in_: *const ffi::fmpz,
) {
let bits = *in_ as u64;
assert!(n >= 0, "negative word count");
assert!(
(n as u128) * 64 >= bits as u128,
"buffer does not cover fmpz_bits"
);
let mut i: isize = 0;
while (i as i64) < n {
*out.offset(i) = kani::any();
i += 1;
}
}
}
#[kani::proof]
fn words_for_bits_full_width() {
let bits: u64 = kani::any();
let n = words_for_bits(bits);
assert!(n as u128 * 64 >= bits as u128);
assert!(n == 0 || (n as u128 - 1) * 64 < bits as u128);
assert!(n as u64 <= i64::MAX as u64);
}
#[kani::proof]
#[kani::unwind(6)]
#[kani::stub(ffi::fmpz_bits, models::fmpz_bits)]
#[kani::stub(ffi::fmpz_get_ui_array, models::fmpz_get_ui_array)]
fn abs_words_buffer_covers_fmpz_bits_small() {
let f: ffi::fmpz = kani::any_where(|b: &ffi::fmpz| (0..=256).contains(b));
let words = unsafe { fmpz_abs_words(&f) };
assert_eq!(words.len() as u64, (f as u64).div_ceil(64));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "divide a FlintInteger by zero")]
fn division_by_zero_panics_instead_of_aborting() {
let _ = FlintInteger::from_i64(7) / FlintInteger::from_i64(0);
}
#[test]
#[should_panic(expected = "divisor of zero")]
fn remainder_by_zero_panics_instead_of_aborting() {
let _ = FlintInteger::from_i64(7) % FlintInteger::from_i64(0);
}
#[test]
fn checked_division() {
let a = FlintInteger::from_i64(-7);
let zero = FlintInteger::from_i64(0);
assert!(a.checked_div(&zero).is_none());
assert!(a.checked_rem(&zero).is_none());
assert_eq!(
a.checked_div(&FlintInteger::from_i64(2)).unwrap().to_i64(),
-3
);
assert_eq!(
a.checked_rem(&FlintInteger::from_i64(2)).unwrap().to_i64(),
-1
);
assert!(zero.is_zero());
assert!(!a.is_zero());
let big = FlintInteger::from_rug(&(rug::Integer::from(1) << 200u32));
assert!(!big.is_zero());
assert!(big.checked_div(&zero).is_none());
}
#[test]
fn zero() {
let z = FlintInteger::new();
assert_eq!(z, FlintInteger::from_i64(0));
}
#[test]
fn from_i64_roundtrip() {
for v in [-1000i64, -1, 0, 1, 1000, i64::MAX / 2] {
let f = FlintInteger::from_i64(v);
assert_eq!(f.to_i64(), v);
}
}
#[test]
fn clone_is_independent() {
let a = FlintInteger::from_i64(42);
let b = a.clone();
assert_eq!(a, b);
let c = &b + &FlintInteger::from_i64(1);
assert_eq!(a, FlintInteger::from_i64(42));
assert_eq!(c, FlintInteger::from_i64(43));
}
#[test]
fn add() {
let a = FlintInteger::from_i64(7);
let b = FlintInteger::from_i64(5);
assert_eq!((&a + &b).to_i64(), 12);
}
#[test]
fn sub() {
let a = FlintInteger::from_i64(7);
let b = FlintInteger::from_i64(5);
assert_eq!((&a - &b).to_i64(), 2);
}
#[test]
fn mul() {
let a = FlintInteger::from_i64(7);
let b = FlintInteger::from_i64(5);
assert_eq!((&a * &b).to_i64(), 35);
}
#[test]
fn div_truncated() {
let a = FlintInteger::from_i64(7);
let b = FlintInteger::from_i64(3);
assert_eq!((&a / &b).to_i64(), 2); let c = FlintInteger::from_i64(-7);
assert_eq!((&c / &b).to_i64(), -2); }
#[test]
fn rem() {
let a = FlintInteger::from_i64(7);
let b = FlintInteger::from_i64(3);
assert_eq!((&a % &b).to_i64(), 1);
}
#[test]
fn neg() {
let a = FlintInteger::from_i64(5);
assert_eq!((-&a).to_i64(), -5);
assert_eq!((-FlintInteger::from_i64(-3)).to_i64(), 3);
}
#[test]
fn gcd() {
let a = FlintInteger::from_i64(12);
let b = FlintInteger::from_i64(8);
assert_eq!(a.gcd(&b).to_i64(), 4);
let p = FlintInteger::from_i64(17);
let q = FlintInteger::from_i64(5);
assert_eq!(p.gcd(&q).to_i64(), 1); }
#[test]
fn pow() {
let a = FlintInteger::from_i64(2);
assert_eq!(a.pow(10).to_i64(), 1024);
assert_eq!(a.pow(0).to_i64(), 1);
}
#[test]
fn display() {
assert_eq!(FlintInteger::from_i64(0).to_string(), "0");
assert_eq!(FlintInteger::from_i64(-42).to_string(), "-42");
assert_eq!(FlintInteger::from_i64(1_000_000).to_string(), "1000000");
}
#[test]
fn roundtrip_vs_rug_small() {
for v in [-999i64, -1, 0, 1, 999] {
let flint = FlintInteger::from_i64(v);
let rug_val = rug::Integer::from(v);
assert_eq!(flint.to_string(), rug_val.to_string(), "mismatch for v={v}");
}
}
#[test]
fn arithmetic_vs_rug() {
use rug::ops::DivRounding;
let pairs: &[(i64, i64)] = &[(0, 0), (7, 5), (-12, 4), (100, 7), (1000, 999)];
for &(a, b) in pairs {
let fa = FlintInteger::from_i64(a);
let fb = FlintInteger::from_i64(b);
let ra = rug::Integer::from(a);
let rb = rug::Integer::from(b);
assert_eq!(
(&fa + &fb).to_string(),
rug::Integer::from(&ra + &rb).to_string(),
"add {a}+{b}"
);
assert_eq!(
(&fa - &fb).to_string(),
rug::Integer::from(&ra - &rb).to_string(),
"sub {a}-{b}"
);
assert_eq!(
(&fa * &fb).to_string(),
rug::Integer::from(&ra * &rb).to_string(),
"mul {a}*{b}"
);
if b != 0 {
let rug_div = ra.clone().div_trunc(rb.clone());
assert_eq!((&fa / &fb).to_string(), rug_div.to_string(), "div {a}/{b}");
}
}
}
#[test]
fn large_integer_vs_rug() {
use rug::ops::Pow;
let two = FlintInteger::from_i64(2);
let big = two.pow(100);
let rug_big = rug::Integer::from(2i64).pow(100u32);
assert_eq!(big.to_string(), rug_big.to_string());
}
fn from_rug_via_string(n: &rug::Integer) -> FlintInteger {
let cstr = std::ffi::CString::new(n.to_string()).unwrap();
let mut f = FlintInteger::new();
assert_eq!(
unsafe { ffi::fmpz_set_str(&mut f.inner, cstr.as_ptr(), 10) },
0
);
f
}
fn to_rug_via_string(f: &FlintInteger) -> rug::Integer {
f.to_string().parse().unwrap()
}
fn check_roundtrip(n: &rug::Integer) {
let f = FlintInteger::from_rug(n);
assert_eq!(f, from_rug_via_string(n), "from_rug disagrees for {n}");
assert_eq!(&f.to_rug(), n, "round trip lost {n}");
assert_eq!(
f.to_rug(),
to_rug_via_string(&f),
"to_rug disagrees for {n}"
);
}
#[test]
fn limb_conversion_edge_values() {
use rug::Integer;
let two64 = Integer::from(1) << 64u32;
let mut cases = vec![
Integer::new(),
Integer::from(1),
Integer::from(-1),
Integer::from(i64::MIN),
Integer::from(i64::MAX),
Integer::from(i64::MIN) - 1u32,
Integer::from(i64::MAX) + 1u32,
Integer::from(u64::MAX),
-Integer::from(u64::MAX),
two64.clone(),
-two64.clone(),
two64.clone() - 1u32,
-(two64.clone() + 1u32),
(Integer::from(1) << 4096u32) - 1u32,
Integer::from(1) << 4095u32,
-(Integer::from(1) << 12_345u32),
];
for k in [61u32, 62, 63] {
cases.push(Integer::from(1) << k);
cases.push(-(Integer::from(1) << k));
cases.push((Integer::from(1) << k) - 1u32);
}
for n in &cases {
check_roundtrip(n);
}
}
fn from_limbs(negative: bool, limbs: &[u64]) -> rug::Integer {
let m = rug::Integer::from_digits(limbs, rug::integer::Order::Lsf);
if negative {
-m
} else {
m
}
}
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn prop_roundtrip_i64(v in any::<i64>()) {
check_roundtrip(&rug::Integer::from(v));
}
#[test]
fn prop_roundtrip_i128(v in any::<i128>()) {
check_roundtrip(&rug::Integer::from(v));
}
#[test]
fn prop_roundtrip_multi_limb(
negative in any::<bool>(),
limbs in prop::collection::vec(any::<u64>(), 0..200),
) {
check_roundtrip(&from_limbs(negative, &limbs));
}
#[test]
fn prop_product_matches_rug(
a_neg in any::<bool>(),
a in prop::collection::vec(any::<u64>(), 0..40),
b_neg in any::<bool>(),
b in prop::collection::vec(any::<u64>(), 0..40),
) {
let (ra, rb) = (from_limbs(a_neg, &a), from_limbs(b_neg, &b));
let prod = &FlintInteger::from_rug(&ra) * &FlintInteger::from_rug(&rb);
prop_assert_eq!(prod.to_rug(), rug::Integer::from(&ra * &rb));
}
#[test]
fn prop_poly_coefficients_roundtrip(
coeffs in prop::collection::vec(
(any::<bool>(), prop::collection::vec(any::<u64>(), 0..20)),
0..8,
),
) {
let rug_coeffs: Vec<rug::Integer> =
coeffs.iter().map(|(neg, l)| from_limbs(*neg, l)).collect();
let p = crate::flint::FlintPoly::from_rug_coefficients(&rug_coeffs);
for (i, c) in rug_coeffs.iter().enumerate() {
let got = p.get_coeff_flint(i);
prop_assert_eq!(&got.to_rug(), c);
prop_assert_eq!(got, from_rug_via_string(c));
}
}
}
#[test]
#[ignore]
fn conversion_timing() {
use std::time::Instant;
for bits in [1_000u32, 10_000, 100_000, 1_000_000] {
let n = (rug::Integer::from(3) << bits) / 7u32 - 1u32;
let reps = (2_000_000 / bits).max(3);
let t = Instant::now();
for _ in 0..reps {
std::hint::black_box(FlintInteger::from_rug(&n));
}
let limb_from = t.elapsed() / reps;
let t = Instant::now();
for _ in 0..reps {
std::hint::black_box(from_rug_via_string(&n));
}
let str_from = t.elapsed() / reps;
let f = FlintInteger::from_rug(&n);
let t = Instant::now();
for _ in 0..reps {
std::hint::black_box(f.to_rug());
}
let limb_to = t.elapsed() / reps;
let t = Instant::now();
for _ in 0..reps {
std::hint::black_box(to_rug_via_string(&f));
}
let str_to = t.elapsed() / reps;
println!(
"{bits:>8} bits from_rug: limb {limb_from:?} vs string {str_from:?} \
to_rug: limb {limb_to:?} vs string {str_to:?}"
);
}
}
}