use core::cmp::Ordering;
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
use num_traits::{
CheckedAdd, CheckedMul, CheckedSub, FromPrimitive, Num, One, Signed, ToPrimitive, Zero,
};
mod mul;
use mul::{mul_full, mul_low};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FixedInt<const K: usize>([u64; K]);
impl<const K: usize> FixedInt<K> {
#[cfg(test)]
#[inline]
pub(crate) const fn from_limbs(limbs: [u64; K]) -> Self {
FixedInt(limbs)
}
#[inline]
pub fn is_zero(&self) -> bool {
self.0.iter().all(|&l| l == 0)
}
#[inline]
pub fn is_one(&self) -> bool {
self.0[0] == 1 && self.0[1..].iter().all(|&l| l == 0)
}
#[inline]
pub fn is_negative(self) -> bool {
self.0[K - 1] >> 63 == 1
}
}
#[inline]
fn is_neg<const K: usize>(a: &[u64; K]) -> bool {
a[K - 1] >> 63 == 1
}
#[inline]
fn negate<const K: usize>(a: &[u64; K]) -> [u64; K] {
let mut out = [0u64; K];
let mut carry = 1u64;
for i in 0..K {
let (v, c) = (!a[i]).overflowing_add(carry);
out[i] = v;
carry = c as u64;
}
out
}
#[inline]
fn bit_length<const K: usize>(a: &[u64; K]) -> usize {
for i in (0..K).rev() {
if a[i] != 0 {
return i * 64 + (64 - a[i].leading_zeros() as usize);
}
}
0
}
#[inline]
fn magnitude<const K: usize>(a: &FixedInt<K>) -> ([u64; K], bool) {
if is_neg(&a.0) {
(negate(&a.0), true)
} else {
(a.0, false)
}
}
#[inline]
fn wrapping_add<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
let mut out = [0u64; K];
let mut carry = 0u64;
for i in 0..K {
let (s1, c1) = a[i].overflowing_add(b[i]);
let (s2, c2) = s1.overflowing_add(carry);
out[i] = s2;
carry = (c1 as u64) | (c2 as u64);
}
out
}
#[inline]
fn wrapping_sub<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
let mut out = [0u64; K];
let mut borrow = 0u64;
for i in 0..K {
let (d1, b1) = a[i].overflowing_sub(b[i]);
let (d2, b2) = d1.overflowing_sub(borrow);
out[i] = d2;
borrow = (b1 as u64) | (b2 as u64);
}
out
}
#[inline]
fn checked_add_limbs<const K: usize>(a: &FixedInt<K>, b: &FixedInt<K>) -> Option<FixedInt<K>> {
let out = wrapping_add(&a.0, &b.0);
let sa = a.0[K - 1] >> 63;
let sb = b.0[K - 1] >> 63;
let sr = out[K - 1] >> 63;
if sa == sb && sr != sa {
None
} else {
Some(FixedInt(out))
}
}
#[inline]
fn checked_sub_limbs<const K: usize>(a: &FixedInt<K>, b: &FixedInt<K>) -> Option<FixedInt<K>> {
let out = wrapping_sub(&a.0, &b.0);
let sa = a.0[K - 1] >> 63;
let sb = b.0[K - 1] >> 63;
let sr = out[K - 1] >> 63;
if sa != sb && sr != sa {
None
} else {
Some(FixedInt(out))
}
}
#[inline]
fn checked_mul_limbs<const K: usize>(a: &FixedInt<K>, b: &FixedInt<K>) -> Option<FixedInt<K>> {
let (ma, sa) = magnitude(a);
let (mb, sb) = magnitude(b);
let la = bit_length(&ma);
let lb = bit_length(&mb);
if la == 0 || lb == 0 {
return Some(FixedInt([0u64; K]));
}
let neg = sa ^ sb;
let w = K * 64;
if la + lb <= w - 1 {
let lo = mul_low(&ma, &mb);
let out = if neg { negate(&lo) } else { lo };
return Some(FixedInt(out));
}
if la + lb >= w + 2 {
return None;
}
debug_assert!(K <= 32, "FixedInt checked_mul scratch supports only K <= 32");
let n2 = 2 * K;
let mut full = [0u64; 64];
mul_full(&ma, &mb, &mut full);
if neg {
let mut c = 1u64;
for slot in full.iter_mut().take(n2) {
let (v, cc) = (!*slot).overflowing_add(c);
*slot = v;
c = cc as u64;
}
}
let ext = if full[K - 1] >> 63 == 1 { u64::MAX } else { 0 };
for &limb in &full[K..n2] {
if limb != ext {
return None;
}
}
let mut out = [0u64; K];
out.copy_from_slice(&full[..K]);
Some(FixedInt(out))
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseFixedIntError;
#[inline]
fn to_le_scratch<const K: usize>(x: &FixedInt<K>) -> [u8; 256] {
debug_assert!(K <= 32, "FixedInt bnum bridge supports only K <= 32");
let mut buf = [0u8; 256];
for i in 0..K {
buf[i * 8..i * 8 + 8].copy_from_slice(&x.0[i].to_le_bytes());
}
buf
}
#[inline]
fn from_le_scratch<const K: usize>(buf: &[u8]) -> FixedInt<K> {
let mut limbs = [0u64; K];
for i in 0..K {
let mut b = [0u8; 8];
b.copy_from_slice(&buf[i * 8..i * 8 + 8]);
limbs[i] = u64::from_le_bytes(b);
}
FixedInt(limbs)
}
macro_rules! bnum_binary {
($K:expr, $ab:expr, $bb:expr, |$x:ident, $y:ident| $op:expr) => {{
let nb = $K * 8;
match $K {
4 => {
let $x = bnum::types::I256::from_le_slice(&$ab[..nb]).unwrap();
let $y = bnum::types::I256::from_le_slice(&$bb[..nb]).unwrap();
from_le_scratch::<$K>(&($op).to_le_bytes())
}
8 => {
let $x = bnum::types::I512::from_le_slice(&$ab[..nb]).unwrap();
let $y = bnum::types::I512::from_le_slice(&$bb[..nb]).unwrap();
from_le_scratch::<$K>(&($op).to_le_bytes())
}
16 => {
let $x = bnum::types::I1024::from_le_slice(&$ab[..nb]).unwrap();
let $y = bnum::types::I1024::from_le_slice(&$bb[..nb]).unwrap();
from_le_scratch::<$K>(&($op).to_le_bytes())
}
32 => {
let $x = bnum::types::I2048::from_le_slice(&$ab[..nb]).unwrap();
let $y = bnum::types::I2048::from_le_slice(&$bb[..nb]).unwrap();
from_le_scratch::<$K>(&($op).to_le_bytes())
}
_ => panic!("FixedInt<{}>: bnum bridge supports only K in {{4,8,16,32}}", $K),
}
}};
}
#[inline]
fn bnum_to_f64<const K: usize>(buf: &[u8]) -> Option<f64> {
let nb = K * 8;
match K {
4 => bnum::types::I256::from_le_slice(&buf[..nb]).unwrap().to_f64(),
8 => bnum::types::I512::from_le_slice(&buf[..nb]).unwrap().to_f64(),
16 => bnum::types::I1024::from_le_slice(&buf[..nb]).unwrap().to_f64(),
32 => bnum::types::I2048::from_le_slice(&buf[..nb]).unwrap().to_f64(),
_ => panic!("FixedInt<{K}>: bnum bridge supports only K in {{4,8,16,32}}"),
}
}
#[inline]
fn bnum_from_str_radix<const K: usize>(
s: &str,
radix: u32,
) -> Result<FixedInt<K>, ParseFixedIntError> {
match K {
4 => bnum::types::I256::from_str_radix(s, radix)
.map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
.map_err(|_| ParseFixedIntError),
8 => bnum::types::I512::from_str_radix(s, radix)
.map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
.map_err(|_| ParseFixedIntError),
16 => bnum::types::I1024::from_str_radix(s, radix)
.map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
.map_err(|_| ParseFixedIntError),
32 => bnum::types::I2048::from_str_radix(s, radix)
.map(|v| from_le_scratch::<K>(&v.to_le_bytes()))
.map_err(|_| ParseFixedIntError),
_ => panic!("FixedInt<{K}>: bnum bridge supports only K in {{4,8,16,32}}"),
}
}
impl<const K: usize> Add for FixedInt<K> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
FixedInt(wrapping_add(&self.0, &rhs.0))
}
}
impl<const K: usize> Sub for FixedInt<K> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
FixedInt(wrapping_sub(&self.0, &rhs.0))
}
}
impl<const K: usize> Mul for FixedInt<K> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
FixedInt(mul_low(&self.0, &rhs.0))
}
}
impl<const K: usize> Neg for FixedInt<K> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
FixedInt(negate(&self.0))
}
}
impl<const K: usize> Div for FixedInt<K> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
let ab = to_le_scratch(&self);
let bb = to_le_scratch(&rhs);
bnum_binary!(K, ab, bb, |x, y| x / y)
}
}
impl<const K: usize> Rem for FixedInt<K> {
type Output = Self;
#[inline]
fn rem(self, rhs: Self) -> Self {
let ab = to_le_scratch(&self);
let bb = to_le_scratch(&rhs);
bnum_binary!(K, ab, bb, |x, y| x % y)
}
}
impl<const K: usize> Zero for FixedInt<K> {
#[inline]
fn zero() -> Self {
FixedInt([0u64; K])
}
#[inline]
fn is_zero(&self) -> bool {
self.0.iter().all(|&l| l == 0)
}
}
impl<const K: usize> One for FixedInt<K> {
#[inline]
fn one() -> Self {
let mut limbs = [0u64; K];
limbs[0] = 1;
FixedInt(limbs)
}
#[inline]
fn is_one(&self) -> bool {
self.0[0] == 1 && self.0[1..].iter().all(|&l| l == 0)
}
}
impl<const K: usize> Num for FixedInt<K> {
type FromStrRadixErr = ParseFixedIntError;
#[inline]
fn from_str_radix(s: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
bnum_from_str_radix::<K>(s, radix)
}
}
impl<const K: usize> Signed for FixedInt<K> {
#[inline]
fn abs(&self) -> Self {
if is_neg(&self.0) {
FixedInt(negate(&self.0))
} else {
*self
}
}
#[inline]
fn abs_sub(&self, other: &Self) -> Self {
if self <= other {
FixedInt([0u64; K])
} else {
*self - *other
}
}
#[inline]
fn signum(&self) -> Self {
if is_neg(&self.0) {
FixedInt([u64::MAX; K]) } else if self.is_zero() {
FixedInt([0u64; K])
} else {
<Self as One>::one()
}
}
#[inline]
fn is_positive(&self) -> bool {
!is_neg(&self.0) && !self.is_zero()
}
#[inline]
fn is_negative(&self) -> bool {
is_neg(&self.0)
}
}
impl<const K: usize> FromPrimitive for FixedInt<K> {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
let mut limbs = if n < 0 { [u64::MAX; K] } else { [0u64; K] };
limbs[0] = n as u64;
Some(FixedInt(limbs))
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
let mut limbs = [0u64; K];
limbs[0] = n;
Some(FixedInt(limbs))
}
}
impl<const K: usize> ToPrimitive for FixedInt<K> {
#[inline]
fn to_i64(&self) -> Option<i64> {
if is_neg(&self.0) {
for i in 1..K {
if self.0[i] != u64::MAX {
return None;
}
}
let v = self.0[0];
if v >> 63 == 1 {
Some(v as i64)
} else {
None
}
} else {
for i in 1..K {
if self.0[i] != 0 {
return None;
}
}
let v = self.0[0];
if v >> 63 == 0 {
Some(v as i64)
} else {
None
}
}
}
#[inline]
fn to_u64(&self) -> Option<u64> {
if is_neg(&self.0) {
return None;
}
for i in 1..K {
if self.0[i] != 0 {
return None;
}
}
Some(self.0[0])
}
#[inline]
fn to_f64(&self) -> Option<f64> {
let buf = to_le_scratch(self);
bnum_to_f64::<K>(&buf)
}
}
impl<const K: usize> CheckedAdd for FixedInt<K> {
#[inline]
fn checked_add(&self, v: &Self) -> Option<Self> {
checked_add_limbs(self, v)
}
}
impl<const K: usize> CheckedSub for FixedInt<K> {
#[inline]
fn checked_sub(&self, v: &Self) -> Option<Self> {
checked_sub_limbs(self, v)
}
}
impl<const K: usize> CheckedMul for FixedInt<K> {
#[inline]
fn checked_mul(&self, v: &Self) -> Option<Self> {
checked_mul_limbs(self, v)
}
}
impl<const K: usize> Ord for FixedInt<K> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match (is_neg(&self.0), is_neg(&other.0)) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => {
for i in (0..K).rev() {
match self.0[i].cmp(&other.0[i]) {
Ordering::Equal => {}
o => return o,
}
}
Ordering::Equal
}
}
}
}
impl<const K: usize> PartialOrd for FixedInt<K> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests;