624 lines
19 KiB
Rust
624 lines
19 KiB
Rust
use std::{
|
|
cmp::Ordering,
|
|
convert::{From, TryFrom},
|
|
fmt::{self, Display, Write},
|
|
mem,
|
|
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
|
|
};
|
|
|
|
use num_bigint::BigInt;
|
|
use num_rational::{BigRational, Rational64};
|
|
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, Zero};
|
|
|
|
use integer::Integer;
|
|
|
|
mod integer;
|
|
|
|
const PRECISION: usize = 10;
|
|
|
|
#[derive(Clone, Eq, PartialEq, Ord)]
|
|
pub(crate) enum Number {
|
|
Small(Rational64),
|
|
Big(Box<BigRational>),
|
|
}
|
|
|
|
impl Number {
|
|
pub const fn new_small(val: Rational64) -> Number {
|
|
Number::Small(val)
|
|
}
|
|
|
|
pub fn new_big(val: BigRational) -> Number {
|
|
Number::Big(Box::new(val))
|
|
}
|
|
|
|
pub fn to_integer(&self) -> Integer {
|
|
match self {
|
|
Self::Small(val) => Integer::Small(val.to_integer()),
|
|
Self::Big(val) => Integer::Big(val.to_integer()),
|
|
}
|
|
}
|
|
|
|
pub fn small_ratio<A: Into<i64>, B: Into<i64>>(a: A, b: B) -> Self {
|
|
Number::new_small(Rational64::new(a.into(), b.into()))
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn big_ratio<A: Into<BigInt>, B: Into<BigInt>>(a: A, b: B) -> Self {
|
|
Number::new_big(BigRational::new(a.into(), b.into()))
|
|
}
|
|
|
|
pub fn round(&self) -> Self {
|
|
match self {
|
|
Self::Small(val) => Self::Small(val.round()),
|
|
Self::Big(val) => Self::Big(Box::new(val.round())),
|
|
}
|
|
}
|
|
|
|
pub fn ceil(&self) -> Self {
|
|
match self {
|
|
Self::Small(val) => Self::Small(val.ceil()),
|
|
Self::Big(val) => Self::Big(Box::new(val.ceil())),
|
|
}
|
|
}
|
|
|
|
pub fn floor(&self) -> Self {
|
|
match self {
|
|
Self::Small(val) => Self::Small(val.floor()),
|
|
Self::Big(val) => Self::Big(Box::new(val.floor())),
|
|
}
|
|
}
|
|
|
|
pub fn abs(&self) -> Self {
|
|
match self {
|
|
Self::Small(val) => Self::Small(val.abs()),
|
|
Self::Big(val) => Self::Big(Box::new(val.abs())),
|
|
}
|
|
}
|
|
|
|
pub fn is_decimal(&self) -> bool {
|
|
match self {
|
|
Self::Small(v) => !v.is_integer(),
|
|
Self::Big(v) => !v.is_integer(),
|
|
}
|
|
}
|
|
|
|
pub fn fract(&mut self) -> Number {
|
|
match self {
|
|
Self::Small(v) => Number::new_small(v.fract()),
|
|
Self::Big(v) => Number::new_big(v.fract()),
|
|
}
|
|
}
|
|
|
|
pub fn clamp<A: Into<Number> + Zero, B: Into<Number>>(self, min: A, max: B) -> Self {
|
|
let max = max.into();
|
|
if self > max {
|
|
return max;
|
|
}
|
|
|
|
if min.is_zero() && self.is_negative() {
|
|
return Number::zero();
|
|
}
|
|
|
|
let min = min.into();
|
|
if self < min {
|
|
return min;
|
|
}
|
|
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Default for Number {
|
|
fn default() -> Self {
|
|
Self::zero()
|
|
}
|
|
}
|
|
|
|
impl Zero for Number {
|
|
fn zero() -> Self {
|
|
Number::new_small(Rational64::from_integer(0))
|
|
}
|
|
|
|
fn is_zero(&self) -> bool {
|
|
match self {
|
|
Self::Small(v) => v.is_zero(),
|
|
Self::Big(v) => v.is_zero(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl One for Number {
|
|
fn one() -> Self {
|
|
Number::new_small(Rational64::from_integer(1))
|
|
}
|
|
|
|
fn is_one(&self) -> bool {
|
|
match self {
|
|
Self::Small(v) => v.is_one(),
|
|
Self::Big(v) => v.is_one(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Num for Number {
|
|
type FromStrRadixErr = ();
|
|
#[cold]
|
|
fn from_str_radix(_: &str, _: u32) -> Result<Self, Self::FromStrRadixErr> {
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
impl Signed for Number {
|
|
fn abs(&self) -> Self {
|
|
self.abs()
|
|
}
|
|
|
|
#[cold]
|
|
fn abs_sub(&self, _: &Self) -> Self {
|
|
unreachable!()
|
|
}
|
|
|
|
#[cold]
|
|
fn signum(&self) -> Self {
|
|
if self.is_zero() {
|
|
Self::zero()
|
|
} else if self.is_positive() {
|
|
Self::one()
|
|
} else {
|
|
-Self::one()
|
|
}
|
|
}
|
|
|
|
fn is_positive(&self) -> bool {
|
|
match self {
|
|
Self::Small(v) => v.is_positive(),
|
|
Self::Big(v) => v.is_positive(),
|
|
}
|
|
}
|
|
|
|
fn is_negative(&self) -> bool {
|
|
match self {
|
|
Self::Small(v) => v.is_negative(),
|
|
Self::Big(v) => v.is_negative(),
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! from_integer {
|
|
($ty:ty) => {
|
|
impl From<$ty> for Number {
|
|
fn from(b: $ty) -> Self {
|
|
if let Ok(v) = i64::try_from(b) {
|
|
Number::Small(Rational64::from_integer(v))
|
|
} else {
|
|
Number::Big(Box::new(BigRational::from_integer(BigInt::from(b))))
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! from_smaller_integer {
|
|
($ty:ty) => {
|
|
impl From<$ty> for Number {
|
|
fn from(val: $ty) -> Self {
|
|
Number::new_small(Rational64::from_integer(val as i64))
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl From<i64> for Number {
|
|
fn from(val: i64) -> Self {
|
|
Number::new_small(Rational64::from_integer(val))
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::fallible_impl_from)]
|
|
impl From<f64> for Number {
|
|
fn from(b: f64) -> Self {
|
|
Number::Big(Box::new(BigRational::from_float(b).unwrap()))
|
|
}
|
|
}
|
|
|
|
from_integer!(usize);
|
|
from_integer!(isize);
|
|
from_smaller_integer!(i32);
|
|
from_smaller_integer!(u32);
|
|
from_smaller_integer!(u8);
|
|
|
|
impl fmt::Debug for Number {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Small(..) => write!(f, "Number::Small( {} )", self),
|
|
Self::Big(..) => write!(f, "Number::Big( {} )", self),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Number {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let mut whole = self.to_integer().abs();
|
|
let has_decimal = self.is_decimal();
|
|
let mut frac = self.abs().fract();
|
|
let mut dec = String::with_capacity(if has_decimal { PRECISION } else { 0 });
|
|
if has_decimal {
|
|
for _ in 0..(PRECISION - 1) {
|
|
frac *= 10_i64;
|
|
write!(dec, "{}", frac.to_integer())?;
|
|
frac = frac.fract();
|
|
if frac.is_zero() {
|
|
break;
|
|
}
|
|
}
|
|
if !frac.is_zero() {
|
|
let end = (frac * 10_i64).round().to_integer();
|
|
if end.is_ten() {
|
|
loop {
|
|
match dec.pop() {
|
|
Some('9') => continue,
|
|
Some(c) => {
|
|
dec.push(char::from(c as u8 + 1));
|
|
break;
|
|
}
|
|
None => {
|
|
whole += 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if end.is_zero() {
|
|
loop {
|
|
match dec.pop() {
|
|
Some('0') => continue,
|
|
Some(c) => {
|
|
dec.push(c);
|
|
break;
|
|
}
|
|
None => break,
|
|
}
|
|
}
|
|
} else {
|
|
write!(dec, "{}", end)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.is_negative() && (!whole.is_zero() || !dec.is_empty()) {
|
|
f.write_char('-')?;
|
|
}
|
|
write!(f, "{}", whole)?;
|
|
if !dec.is_empty() {
|
|
f.write_char('.')?;
|
|
write!(f, "{}", dec)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for Number {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
Self::Small(val2) => val1.partial_cmp(val2),
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = (*val1).into();
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1))
|
|
.partial_cmp(val2)
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = (*val2).into();
|
|
(**val1).partial_cmp(&BigRational::new_raw(
|
|
BigInt::from(tuple.0),
|
|
BigInt::from(tuple.1),
|
|
))
|
|
}
|
|
Self::Big(val2) => val1.partial_cmp(val2),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Add for Number {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
Self::Small(val2) => match val1.checked_add(&val2) {
|
|
Some(v) => Self::Small(v),
|
|
None => {
|
|
let tuple1: (i64, i64) = val1.into();
|
|
let tuple2: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple1.0), BigInt::from(tuple1.1))
|
|
+ BigRational::new_raw(
|
|
BigInt::from(tuple2.0),
|
|
BigInt::from(tuple2.1),
|
|
),
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = val1.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)) + *val2,
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Big(val2) => Self::Big(Box::new(*val1 + *val2)),
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
(*val1)
|
|
+ BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)),
|
|
))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Add<&Self> for Number {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: &Self) -> Self {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
Self::Small(val2) => match val1.checked_add(val2) {
|
|
Some(v) => Self::Small(v),
|
|
None => {
|
|
let tuple1: (i64, i64) = val1.into();
|
|
let tuple2: (i64, i64) = (*val2).into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple1.0), BigInt::from(tuple1.1))
|
|
+ BigRational::new_raw(
|
|
BigInt::from(tuple2.0),
|
|
BigInt::from(tuple2.1),
|
|
),
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = val1.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1))
|
|
+ *val2.clone(),
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Big(val2) => Self::Big(Box::new(*val1 + *val2.clone())),
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = (*val2).into();
|
|
Self::Big(Box::new(
|
|
*val1 + BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)),
|
|
))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AddAssign for Number {
|
|
fn add_assign(&mut self, other: Self) {
|
|
let tmp = mem::take(self);
|
|
*self = tmp + other;
|
|
}
|
|
}
|
|
|
|
impl Sub for Number {
|
|
type Output = Self;
|
|
|
|
fn sub(self, other: Self) -> Self {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
Self::Small(val2) => match val1.checked_sub(&val2) {
|
|
Some(v) => Self::Small(v),
|
|
None => {
|
|
let tuple1: (i64, i64) = val1.into();
|
|
let tuple2: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple1.0), BigInt::from(tuple1.1))
|
|
- BigRational::new_raw(
|
|
BigInt::from(tuple2.0),
|
|
BigInt::from(tuple2.1),
|
|
),
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = val1.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)) - *val2,
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Big(val2) => Self::Big(Box::new(*val1 - *val2)),
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
*val1 - BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)),
|
|
))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SubAssign for Number {
|
|
fn sub_assign(&mut self, other: Self) {
|
|
let tmp = mem::take(self);
|
|
*self = tmp - other;
|
|
}
|
|
}
|
|
|
|
impl Mul for Number {
|
|
type Output = Self;
|
|
|
|
fn mul(self, other: Self) -> Self {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
Self::Small(val2) => match val1.checked_mul(&val2) {
|
|
Some(v) => Self::Small(v),
|
|
None => {
|
|
let tuple1: (i64, i64) = val1.into();
|
|
let tuple2: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple1.0), BigInt::from(tuple1.1))
|
|
* BigRational::new_raw(
|
|
BigInt::from(tuple2.0),
|
|
BigInt::from(tuple2.1),
|
|
),
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = val1.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)) * *val2,
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Big(val2) => Self::Big(Box::new(*val1 * *val2)),
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
*val1 * BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)),
|
|
))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Mul<i64> for Number {
|
|
type Output = Self;
|
|
|
|
fn mul(self, other: i64) -> Self {
|
|
match self {
|
|
Self::Small(val1) => Self::Small(val1 * other),
|
|
Self::Big(val1) => Self::Big(Box::new(*val1 * BigInt::from(other))),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MulAssign<i64> for Number {
|
|
fn mul_assign(&mut self, other: i64) {
|
|
let tmp = mem::take(self);
|
|
*self = tmp * other;
|
|
}
|
|
}
|
|
|
|
impl MulAssign for Number {
|
|
fn mul_assign(&mut self, other: Self) {
|
|
let tmp = mem::take(self);
|
|
*self = tmp * other;
|
|
}
|
|
}
|
|
|
|
impl Div for Number {
|
|
type Output = Self;
|
|
|
|
fn div(self, other: Self) -> Self {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
Self::Small(val2) => match val1.checked_div(&val2) {
|
|
Some(v) => Self::Small(v),
|
|
None => {
|
|
let tuple1: (i64, i64) = val1.into();
|
|
let tuple2: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple1.0), BigInt::from(tuple1.1))
|
|
/ BigRational::new_raw(
|
|
BigInt::from(tuple2.0),
|
|
BigInt::from(tuple2.1),
|
|
),
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = val1.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)) / *val2,
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Big(val2) => Self::Big(Box::new(*val1 / *val2)),
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
*val1 / BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)),
|
|
))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DivAssign for Number {
|
|
fn div_assign(&mut self, other: Self) {
|
|
let tmp = mem::take(self);
|
|
*self = tmp / other;
|
|
}
|
|
}
|
|
|
|
impl Rem for Number {
|
|
type Output = Self;
|
|
|
|
fn rem(self, other: Self) -> Self {
|
|
match self {
|
|
Self::Small(val1) => match other {
|
|
// todo: checked_rem for ratio?
|
|
Self::Small(val2) => {
|
|
let tuple1: (i64, i64) = val1.into();
|
|
let tuple2: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple1.0), BigInt::from(tuple1.1))
|
|
% BigRational::new_raw(BigInt::from(tuple2.0), BigInt::from(tuple2.1)),
|
|
))
|
|
}
|
|
Self::Big(val2) => {
|
|
let tuple: (i64, i64) = val1.into();
|
|
Self::Big(Box::new(
|
|
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)) % *val2,
|
|
))
|
|
}
|
|
},
|
|
Self::Big(val1) => match other {
|
|
Self::Big(val2) => Self::Big(Box::new(*val1 % *val2)),
|
|
Self::Small(val2) => {
|
|
let tuple: (i64, i64) = val2.into();
|
|
Self::Big(Box::new(
|
|
*val1 % BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)),
|
|
))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RemAssign for Number {
|
|
fn rem_assign(&mut self, other: Self) {
|
|
let tmp = mem::take(self);
|
|
*self = tmp % other;
|
|
}
|
|
}
|
|
|
|
impl Neg for Number {
|
|
type Output = Self;
|
|
|
|
fn neg(self) -> Self {
|
|
match self {
|
|
Self::Small(v) => Self::Small(-v),
|
|
Self::Big(v) => Self::Big(Box::new(-*v)),
|
|
}
|
|
}
|
|
}
|