use std::cmp::{Ordering, PartialOrd};
use std::ops::{Add, Div, Mul, Neg, Sub};
use crate::bigint::BigInt;
use crate::bigrat::BigRat;
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(into = "NumericParts")]
pub enum Numeric {
Rational(BigRat),
Float(f64),
}
enum Parity {
Rational(BigRat, BigRat),
Float(f64, f64),
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub enum Digits {
Default,
FullInt,
Digits(u64),
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NumericParts {
numer: String,
denom: String,
exact_value: Option<String>,
approx_value: Option<String>,
}
impl Numeric {
pub fn one() -> Numeric {
Numeric::Rational(BigRat::one())
}
pub fn zero() -> Numeric {
Numeric::Rational(BigRat::zero())
}
pub fn abs(&self) -> Numeric {
match *self {
Numeric::Rational(ref rational) => Numeric::Rational(rational.abs()),
Numeric::Float(f) => Numeric::Float(f.abs()),
}
}
fn parity(&self, other: &Numeric) -> Parity {
match (self, other) {
(&Numeric::Float(left), right) => Parity::Float(left, right.into()),
(left, &Numeric::Float(right)) => Parity::Float(left.into(), right),
(&Numeric::Rational(ref left), &Numeric::Rational(ref right)) => {
Parity::Rational(left.clone(), right.clone())
}
}
}
pub fn div_rem(&self, other: &Numeric) -> (Numeric, Numeric) {
match self.parity(other) {
Parity::Rational(left, right) => {
let div = &left / &right;
let floor = &div.numer() / &div.denom();
let rem = &left - &(&right * &BigRat::ratio(&floor, &BigInt::one()));
(
Numeric::Rational(BigRat::ratio(&floor, &BigInt::one())),
Numeric::Rational(rem),
)
}
Parity::Float(left, right) => {
(Numeric::Float(left / right), Numeric::Float(left % right))
}
}
}
pub fn to_rational(&self) -> (BigInt, BigInt) {
match *self {
Numeric::Rational(ref rational) => (rational.numer(), rational.denom()),
Numeric::Float(x) => {
let rational = BigRat::from(x);
(rational.numer(), rational.denom())
}
}
}
pub fn to_int(&self) -> Option<i64> {
match *self {
Numeric::Rational(ref rational) => (&rational.numer() / &rational.denom()).as_int(),
Numeric::Float(f) => {
if !f.is_nan() && !f.is_infinite() && f.abs() < i64::max_value() as f64 {
Some(f as i64)
} else {
None
}
}
}
}
pub fn to_f64(&self) -> f64 {
self.into()
}
pub fn to_string(&self, base: u8, digits: Digits) -> (bool, String) {
use std::char::from_digit;
use std::num::FpCategory;
if let Numeric::Float(value) = *self {
match value.classify() {
FpCategory::Nan => return (false, "NaN".to_owned()),
FpCategory::Infinite if value.is_sign_positive() => {
return (false, "Inf".to_owned())
}
FpCategory::Infinite => return (false, "-Inf".to_owned()),
_ => (),
}
}
let sign = *self < Numeric::zero();
let rational = self.abs();
let (num, den) = rational.to_rational();
let rational = match rational {
Numeric::Rational(rational) => rational,
Numeric::Float(f) => BigRat::from(f),
};
let intdigits = (&num / &den).size_in_base(base) as u32;
let mut buf = String::new();
if sign {
buf.push('-');
}
let zero = BigRat::zero();
let one = BigInt::one();
let ten = BigInt::from(base as u64);
let ten_rational = BigRat::ratio(&ten, &one);
let mut cursor = &rational / &BigRat::ratio(&ten.pow(intdigits), &one);
let mut n = 0;
let mut only_zeros = true;
let mut zeros = 0;
let mut placed_decimal = false;
loop {
let exact = cursor == zero;
let use_sci = if digits != Digits::Default
|| den == one && (base == 2 || base == 8 || base == 16 || base == 32)
{
false
} else {
intdigits + zeros > 9 * 10 / base as u32
};
let placed_ints = n >= intdigits;
let ndigits = match digits {
Digits::Default | Digits::FullInt => 6,
Digits::Digits(n) => intdigits as i32 + n as i32,
};
let bail = (exact && (placed_ints || use_sci))
|| (n as i32 - zeros as i32 > ndigits && use_sci)
|| n as i32 - zeros as i32 > ::std::cmp::max(intdigits as i32, ndigits);
if bail && use_sci {
let off = if n < intdigits { 0 } else { zeros };
buf = buf[off as usize + placed_decimal as usize + sign as usize..].to_owned();
buf.insert(1, '.');
if buf.len() == 2 {
buf.insert(2, '0');
}
if sign {
buf.insert(0, '-');
}
buf.push_str(&*format!("e{}", intdigits as i32 - zeros as i32 - 1));
return (exact, buf);
}
if bail {
return (exact, buf);
}
if n == intdigits {
buf.push('.');
placed_decimal = true;
}
let digit = &(&(&cursor.numer() * &ten) / &cursor.denom()) % &ten;
let v: Option<i64> = digit.as_int();
let v = v.unwrap();
if v != 0 {
only_zeros = false
} else if only_zeros {
zeros += 1;
}
if !(v == 0 && only_zeros && n < intdigits - 1) {
buf.push(from_digit(v as u32, base as u32).unwrap());
}
cursor = &cursor * &ten_rational;
cursor = &cursor - &BigRat::ratio(&digit, &one);
n += 1;
}
}
pub fn string_repr(&self, base: u8, digits: Digits) -> (Option<String>, Option<String>) {
match *self {
Numeric::Rational(ref rational) => {
let num = rational.numer();
let den = rational.denom();
match self.to_string(base, digits) {
(true, v) => (Some(v), None),
(false, v) => {
if den > BigInt::from(1_000u64) || num > BigInt::from(1_000_000u64) {
(None, Some(v))
} else {
(Some(format!("{}/{}", num, den)), Some(v))
}
}
}
}
Numeric::Float(_f) => (None, Some(self.to_string(base, digits).1)),
}
}
}
impl From<BigRat> for Numeric {
fn from(rat: BigRat) -> Numeric {
Numeric::Rational(rat)
}
}
impl From<BigInt> for Numeric {
fn from(int: BigInt) -> Numeric {
Numeric::Rational(BigRat::ratio(&int, &BigInt::one()))
}
}
impl From<i64> for Numeric {
fn from(i: i64) -> Numeric {
Numeric::from(BigInt::from(i))
}
}
impl<'a> From<&'a Numeric> for f64 {
fn from(value: &'a Numeric) -> f64 {
match value {
Numeric::Rational(ref rational) => rational.as_float(),
Numeric::Float(f) => *f,
}
}
}
impl From<Numeric> for NumericParts {
fn from(value: Numeric) -> NumericParts {
let (exact, approx) = value.string_repr(10, Digits::Default);
let (num, den) = value.to_rational();
NumericParts {
numer: num.to_string(),
denom: den.to_string(),
exact_value: exact,
approx_value: approx,
}
}
}
impl PartialOrd for Numeric {
fn partial_cmp(&self, other: &Numeric) -> Option<Ordering> {
match self.parity(other) {
Parity::Rational(left, right) => left.partial_cmp(&right),
Parity::Float(left, right) => left.partial_cmp(&right),
}
}
}
macro_rules! num_binop {
($what:ident, $func:ident) => {
impl<'a, 'b> $what<&'b Numeric> for &'a Numeric {
type Output = Numeric;
fn $func(self, other: &'b Numeric) -> Numeric {
match self.parity(other) {
Parity::Rational(left, right) => Numeric::Rational(left.$func(&right)),
Parity::Float(left, right) => Numeric::Float(left.$func(&right)),
}
}
}
};
}
num_binop!(Add, add);
num_binop!(Sub, sub);
num_binop!(Mul, mul);
num_binop!(Div, div);
impl<'a> Neg for &'a Numeric {
type Output = Numeric;
fn neg(self) -> Numeric {
match *self {
Numeric::Rational(ref rational) => Numeric::Rational(-rational),
Numeric::Float(f) => Numeric::Float(-f),
}
}
}