use core::cmp::Ordering;
use core::fmt;
use core::str::FromStr;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{FromPrimitive, ToPrimitive, Zero};
#[derive(Clone)]
pub enum AverInt {
Small(i64),
Big(Box<BigInt>),
}
impl AverInt {
#[inline]
pub const fn from_i64(n: i64) -> Self {
AverInt::Small(n)
}
#[inline]
pub const fn zero() -> Self {
AverInt::Small(0)
}
#[inline]
pub fn from_bigint(b: BigInt) -> Self {
match b.to_i64() {
Some(n) => AverInt::Small(n),
None => AverInt::Big(Box::new(b)),
}
}
#[inline]
fn to_bigint(&self) -> BigInt {
match self {
AverInt::Small(n) => BigInt::from(*n),
AverInt::Big(b) => (**b).clone(),
}
}
#[inline]
pub fn is_zero(&self) -> bool {
match self {
AverInt::Small(n) => *n == 0,
AverInt::Big(b) => b.is_zero(),
}
}
pub fn add(&self, rhs: &AverInt) -> AverInt {
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(s) = a.checked_add(*b) {
return AverInt::Small(s);
}
}
AverInt::from_bigint(self.to_bigint() + rhs.to_bigint())
}
pub fn sub(&self, rhs: &AverInt) -> AverInt {
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(s) = a.checked_sub(*b) {
return AverInt::Small(s);
}
}
AverInt::from_bigint(self.to_bigint() - rhs.to_bigint())
}
pub fn mul(&self, rhs: &AverInt) -> AverInt {
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(s) = a.checked_mul(*b) {
return AverInt::Small(s);
}
}
AverInt::from_bigint(self.to_bigint() * rhs.to_bigint())
}
pub fn neg(&self) -> AverInt {
match self {
AverInt::Small(n) => match n.checked_neg() {
Some(v) => AverInt::Small(v),
None => AverInt::from_bigint(-BigInt::from(*n)),
},
AverInt::Big(b) => AverInt::from_bigint(-(**b).clone()),
}
}
pub fn div_euclid(&self, rhs: &AverInt) -> Option<AverInt> {
if rhs.is_zero() {
return None;
}
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(q) = a.checked_div_euclid(*b) {
return Some(AverInt::Small(q));
}
}
let (q, _r) = euclid_div_rem(&self.to_bigint(), &rhs.to_bigint());
Some(AverInt::from_bigint(q))
}
pub fn rem_euclid(&self, rhs: &AverInt) -> Option<AverInt> {
if rhs.is_zero() {
return None;
}
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(r) = a.checked_rem_euclid(*b) {
return Some(AverInt::Small(r));
}
}
let (_q, r) = euclid_div_rem(&self.to_bigint(), &rhs.to_bigint());
Some(AverInt::from_bigint(r))
}
pub fn div_trunc(&self, rhs: &AverInt) -> Option<AverInt> {
if rhs.is_zero() {
return None;
}
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(q) = a.checked_div(*b) {
return Some(AverInt::Small(q));
}
}
Some(AverInt::from_bigint(self.to_bigint() / rhs.to_bigint()))
}
pub fn rem_trunc(&self, rhs: &AverInt) -> Option<AverInt> {
if rhs.is_zero() {
return None;
}
if let (AverInt::Small(a), AverInt::Small(b)) = (self, rhs) {
if let Some(r) = a.checked_rem(*b) {
return Some(AverInt::Small(r));
}
}
Some(AverInt::from_bigint(self.to_bigint() % rhs.to_bigint()))
}
pub fn abs(&self) -> AverInt {
match self {
AverInt::Small(n) => match n.checked_abs() {
Some(v) => AverInt::Small(v),
None => AverInt::from_bigint(BigInt::from(*n).magnitude().clone().into()),
},
AverInt::Big(b) => AverInt::from_bigint(BigInt::from(b.magnitude().clone())),
}
}
pub fn min_ref(&self, other: &AverInt) -> AverInt {
if self <= other {
self.clone()
} else {
other.clone()
}
}
pub fn max_ref(&self, other: &AverInt) -> AverInt {
if self >= other {
self.clone()
} else {
other.clone()
}
}
#[inline]
pub fn to_i64(&self) -> Option<i64> {
match self {
AverInt::Small(n) => Some(*n),
AverInt::Big(b) => b.to_i64(),
}
}
#[inline]
pub fn to_usize(&self) -> Option<usize> {
match self {
AverInt::Small(n) => usize::try_from(*n).ok(),
AverInt::Big(b) => b.to_usize(),
}
}
#[inline]
pub fn to_u16(&self) -> Option<u16> {
match self {
AverInt::Small(n) => u16::try_from(*n).ok(),
AverInt::Big(b) => b.to_u16(),
}
}
#[inline]
pub fn to_u32(&self) -> Option<u32> {
match self {
AverInt::Small(n) => u32::try_from(*n).ok(),
AverInt::Big(b) => b.to_u32(),
}
}
#[inline]
pub fn to_f64(&self) -> f64 {
match self {
AverInt::Small(n) => *n as f64,
AverInt::Big(b) => b.to_f64().unwrap_or(f64::INFINITY),
}
}
pub fn from_f64_trunc(f: f64) -> AverInt {
if !f.is_finite() {
return AverInt::zero();
}
let truncated = f.trunc();
match truncated.to_i64() {
Some(n) => AverInt::Small(n),
None => match BigInt::from_f64(truncated) {
Some(b) => AverInt::from_bigint(b),
None => AverInt::zero(),
},
}
}
}
fn euclid_div_rem(a: &BigInt, b: &BigInt) -> (BigInt, BigInt) {
let (q, r) = a.div_rem(b);
if r.sign() == num_bigint::Sign::Minus {
if b.sign() == num_bigint::Sign::Plus {
(q - 1, r + b)
} else {
(q + 1, r - b)
}
} else {
(q, r)
}
}
impl PartialEq for AverInt {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(AverInt::Small(a), AverInt::Small(b)) => a == b,
(AverInt::Big(a), AverInt::Big(b)) => a == b,
_ => false,
}
}
}
impl Eq for AverInt {}
impl Ord for AverInt {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(AverInt::Small(a), AverInt::Small(b)) => a.cmp(b),
(AverInt::Big(a), AverInt::Big(b)) => a.cmp(b),
(AverInt::Small(_), AverInt::Big(b)) => {
if b.sign() == num_bigint::Sign::Minus {
Ordering::Greater
} else {
Ordering::Less
}
}
(AverInt::Big(a), AverInt::Small(_)) => {
if a.sign() == num_bigint::Sign::Minus {
Ordering::Less
} else {
Ordering::Greater
}
}
}
}
}
impl PartialOrd for AverInt {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl core::hash::Hash for AverInt {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match self {
AverInt::Small(n) => n.hash(state),
AverInt::Big(b) => b.hash(state),
}
}
}
impl fmt::Display for AverInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AverInt::Small(n) => write!(f, "{}", n),
AverInt::Big(b) => write!(f, "{}", b),
}
}
}
impl fmt::Debug for AverInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AverInt::Small(n) => write!(f, "{}", n),
AverInt::Big(b) => write!(f, "{}", b),
}
}
}
impl FromStr for AverInt {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(n) = s.parse::<i64>() {
return Ok(AverInt::Small(n));
}
match BigInt::from_str(s) {
Ok(b) => Ok(AverInt::from_bigint(b)),
Err(_) => Err(()),
}
}
}
impl From<i64> for AverInt {
#[inline]
fn from(n: i64) -> Self {
AverInt::Small(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn big(s: &str) -> AverInt {
AverInt::from_str(s).unwrap()
}
#[test]
fn canonical_form_demotes_to_small() {
let a = AverInt::from_i64(i64::MAX);
let doubled = a.add(&a);
assert!(matches!(doubled, AverInt::Big(_)));
let halved = doubled.div_euclid(&AverInt::from_i64(2)).unwrap();
assert_eq!(halved, AverInt::from_i64(i64::MAX));
assert!(matches!(halved, AverInt::Small(_)));
}
#[test]
fn square_is_non_negative_past_i64() {
let a = AverInt::from_i64(i64::MAX);
let sq = a.mul(&a);
assert!(matches!(sq, AverInt::Big(_)));
assert!(sq >= AverInt::zero());
}
#[test]
fn equal_bigs_built_differently_are_equal_and_hash_equal() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let big_a = AverInt::from_i64(i64::MAX).add(&AverInt::from_i64(1));
let big_b = big("9223372036854775808");
assert_eq!(big_a, big_b);
let mut ha = DefaultHasher::new();
let mut hb = DefaultHasher::new();
big_a.hash(&mut ha);
big_b.hash(&mut hb);
assert_eq!(ha.finish(), hb.finish());
}
#[test]
fn from_bigint_demotes_in_range_value_to_small() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let from_big = AverInt::from_bigint(BigInt::from(5));
let small = AverInt::from_i64(5);
assert!(matches!(from_big, AverInt::Small(5)));
assert_eq!(from_big, small);
let mut hb = DefaultHasher::new();
let mut hs = DefaultHasher::new();
from_big.hash(&mut hb);
small.hash(&mut hs);
assert_eq!(hb.finish(), hs.finish());
assert!(matches!(
AverInt::from_bigint(BigInt::from(i64::MAX)),
AverInt::Small(i64::MAX)
));
assert!(matches!(
AverInt::from_bigint(BigInt::from(i64::MIN)),
AverInt::Small(i64::MIN)
));
let past = AverInt::from_bigint(BigInt::from(i64::MAX) + 1);
assert!(matches!(past, AverInt::Big(_)));
}
#[test]
fn euclidean_div_mod_match_i64_in_range() {
for a in [-7i64, -1, 0, 1, 7, 100] {
for b in [-3i64, -1, 1, 3, 5] {
let ai = AverInt::from_i64(a);
let bi = AverInt::from_i64(b);
assert_eq!(
ai.div_euclid(&bi).unwrap(),
AverInt::from_i64(a.div_euclid(b))
);
assert_eq!(
ai.rem_euclid(&bi).unwrap(),
AverInt::from_i64(a.rem_euclid(b))
);
}
}
}
#[test]
fn euclidean_div_mod_big_branch_negative_divisor() {
let two_63 = AverInt::from_i64(i64::MAX).add(&AverInt::from_i64(1));
assert!(matches!(two_63, AverInt::Big(_)));
let neg3 = AverInt::from_i64(-3);
let q = two_63.div_euclid(&neg3).unwrap();
let r = two_63.rem_euclid(&neg3).unwrap();
assert_eq!(q, big("-3074457345618258602"));
assert_eq!(r, AverInt::from_i64(2));
assert!(r >= AverInt::zero());
}
#[test]
fn euclidean_div_mod_big_full_sign_matrix() {
let pos = big("9223372036854775808"); let neg = big("-9223372036854775809"); let dpos = big("100000000000000000000"); let dneg = big("-100000000000000000000");
for a in [&pos, &neg] {
for b in [&dpos, &dneg] {
assert!(matches!(*a, AverInt::Big(_)));
assert!(matches!(*b, AverInt::Big(_)));
let q = a.div_euclid(b).unwrap();
let r = a.rem_euclid(b).unwrap();
assert!(r >= AverInt::zero(), "remainder negative for {a}/{b}");
assert!(r < b.abs(), "remainder >= |divisor| for {a}/{b}");
assert_eq!(&b.mul(&q).add(&r), a, "identity broken for {a}/{b}");
}
}
}
#[test]
fn div_by_zero_is_none() {
assert!(AverInt::from_i64(5).div_euclid(&AverInt::zero()).is_none());
assert!(AverInt::from_i64(5).rem_euclid(&AverInt::zero()).is_none());
}
#[test]
fn min_div_neg_one_promotes_not_panics() {
let min = AverInt::from_i64(i64::MIN);
let q = min.div_euclid(&AverInt::from_i64(-1)).unwrap();
assert!(matches!(q, AverInt::Big(_)));
assert_eq!(q, big("9223372036854775808"));
}
#[test]
fn abs_of_min_promotes() {
let q = AverInt::from_i64(i64::MIN).abs();
assert_eq!(q, big("9223372036854775808"));
}
#[test]
fn checked_conversions_reject_out_of_range() {
let huge = big("99999999999999999999999999");
assert_eq!(huge.to_i64(), None);
assert_eq!(huge.to_usize(), None);
assert_eq!(huge.to_u32(), None);
assert_eq!(huge.to_u16(), None);
assert_eq!(AverInt::from_i64(-1).to_usize(), None);
assert_eq!(AverInt::from_i64(70000).to_u16(), None);
}
#[test]
fn to_f64_saturates_to_infinity() {
let huge = big("1").mul(&big("10").mul(&big("10"))); assert_eq!(huge.to_f64(), 100.0);
let enormous = AverInt::from_i64(10).mul(&AverInt::from_i64(10));
assert_eq!(enormous.to_f64(), 100.0);
let mut p = AverInt::from_i64(1);
let ten = AverInt::from_i64(10);
for _ in 0..400 {
p = p.mul(&ten);
}
assert!(p.to_f64().is_infinite() && p.to_f64() > 0.0);
assert!(p.neg().to_f64().is_infinite() && p.neg().to_f64() < 0.0);
}
#[test]
fn parse_roundtrip_past_i64() {
let s = "170141183460469231731687303715884105727"; let v = big(s);
assert!(matches!(v, AverInt::Big(_)));
assert_eq!(v.to_string(), s);
}
#[test]
fn from_str_rejects_garbage() {
assert!(AverInt::from_str("").is_err());
assert!(AverInt::from_str("12x").is_err());
assert!(AverInt::from_str("1.5").is_err());
}
#[test]
fn from_f64_trunc_preserves_huge_finite_magnitudes() {
let v = AverInt::from_f64_trunc(1e20);
assert!(matches!(v, AverInt::Big(_)));
assert_eq!(v.to_string(), "100000000000000000000");
let n = AverInt::from_f64_trunc(-1e20);
assert_eq!(n.to_string(), "-100000000000000000000");
}
#[test]
fn from_f64_trunc_truncates_toward_zero_in_range() {
assert_eq!(AverInt::from_f64_trunc(3.9), AverInt::from_i64(3));
assert_eq!(AverInt::from_f64_trunc(-3.9), AverInt::from_i64(-3));
assert_eq!(AverInt::from_f64_trunc(0.0), AverInt::zero());
}
#[test]
fn from_f64_trunc_non_finite_is_zero() {
assert_eq!(AverInt::from_f64_trunc(f64::NAN), AverInt::zero());
assert_eq!(AverInt::from_f64_trunc(f64::INFINITY), AverInt::zero());
assert_eq!(AverInt::from_f64_trunc(f64::NEG_INFINITY), AverInt::zero());
}
}