#![cfg_attr(not(feature = "arbitrary_precision"), allow(dead_code))]
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hasher;
use std::num::NonZeroU128;
use super::bigint;
#[derive(Clone, Copy)]
pub(crate) struct NumVal<'a>(Repr<'a>);
#[derive(Clone, Copy)]
enum Repr<'a> {
Int(i64),
UInt(u64),
Float(f64),
Decimal { mantissa: i64, exp: i32 },
Big(BigDec<'a>),
}
#[derive(Clone, Copy)]
pub(crate) struct BigDec<'a> {
negative: bool,
magnitude: &'a [u64],
exp: i32,
}
struct OwnedBigDec {
negative: bool,
magnitude: Vec<u64>,
exp: i32,
}
impl OwnedBigDec {
fn as_ref(&self) -> BigDec<'_> {
BigDec {
negative: self.negative,
magnitude: &self.magnitude,
exp: self.exp,
}
}
}
pub(crate) struct Canonical {
pub(crate) negative: bool,
pub(crate) magnitude: Vec<u64>,
pub(crate) exp: i32,
pub(crate) small: Option<NumVal<'static>>,
}
impl<'a> NumVal<'a> {
pub(crate) fn from_i64(x: i64) -> NumVal<'static> {
NumVal(Repr::Int(x))
}
pub(crate) fn from_u64(x: u64) -> NumVal<'static> {
match i64::try_from(x) {
Ok(i) => NumVal(Repr::Int(i)),
Err(_) => NumVal(Repr::UInt(x)),
}
}
pub(crate) fn from_f64(x: f64) -> NumVal<'static> {
if x.fract() == 0.0 {
if (-I64_RANGE..I64_RANGE).contains(&x) {
return NumVal(Repr::Int(x as i64));
}
if (0.0..U64_RANGE).contains(&x) {
return NumVal(Repr::UInt(x as u64));
}
}
NumVal(Repr::Float(x))
}
#[inline]
pub(crate) fn from_decimal(mantissa: i64, exp: i32) -> NumVal<'static> {
debug_assert!(
fits_decimal(mantissa, i64::from(exp)),
"{}e{} is outside the `Decimal` domain, so its exact arithmetic may overflow",
mantissa,
exp
);
if let Some(v) = decimal_int_value(mantissa, exp) {
if let Ok(i) = i64::try_from(v) {
return NumVal(Repr::Int(i));
}
if let Ok(u) = u64::try_from(v) {
return NumVal(Repr::UInt(u));
}
}
if let Some(f) = decimal_to_f64_exact(mantissa, exp) {
return NumVal(Repr::Float(f));
}
let (mantissa, exp) = canonical_decimal(mantissa, exp);
NumVal(Repr::Decimal { mantissa, exp })
}
pub(crate) fn from_big(negative: bool, magnitude: &'a [u64], exp: i32) -> NumVal<'a> {
debug_assert!(
!bigint::is_zero(magnitude) && bigint::rem_small(magnitude, bigint::TEN) != 0,
"a stored decimal's magnitude is canonical: non-zero and not divisible by ten"
);
if let Some(nv) = reduce_fixed(negative, magnitude, exp) {
return nv;
}
NumVal(Repr::Big(BigDec {
negative,
magnitude,
exp,
}))
}
pub(crate) fn to_i64(self) -> Option<i64> {
match self.0 {
Repr::Int(x) => Some(x),
_ => None,
}
}
pub(crate) fn to_u64(self) -> Option<u64> {
match self.0 {
Repr::Int(x) => u64::try_from(x).ok(),
Repr::UInt(x) => Some(x),
_ => None,
}
}
pub(crate) fn to_f64(self) -> Option<f64> {
match self.0 {
Repr::Int(x) => can_represent_as_f64(x.unsigned_abs()).then_some(x as f64),
Repr::UInt(x) => can_represent_as_f64(x).then_some(x as f64),
Repr::Float(x) => Some(x),
Repr::Decimal { .. } | Repr::Big(_) => None,
}
}
#[inline]
pub(crate) fn to_f64_lossy(self) -> f64 {
match self.0 {
Repr::Int(x) => x as f64,
Repr::UInt(x) => x as f64,
Repr::Float(x) => x,
Repr::Decimal { mantissa, exp } => decimal_to_f64_lossy(mantissa, exp),
Repr::Big(b) => b.to_f64_lossy(),
}
}
pub(crate) fn hash(self, state: &mut dyn Hasher) {
match self.0 {
Repr::Int(x) => state.write_i64(x),
Repr::UInt(x) => state.write_u64(x),
Repr::Float(x) => state.write_u64(x.to_bits()),
Repr::Decimal { mantissa, exp } => {
state.write_i64(mantissa);
state.write_i32(exp);
}
Repr::Big(b) => {
state.write_u8(u8::from(b.negative));
state.write_i32(b.exp);
for &limb in b.magnitude {
state.write_u64(limb);
}
}
}
}
pub(crate) fn cmp(self, other: NumVal<'_>) -> Ordering {
use Repr::{Big, Decimal, Float, Int, UInt};
match (self.0, other.0) {
(Int(x), Int(y)) => x.cmp(&y),
(UInt(x), UInt(y)) => x.cmp(&y),
(Int(x), UInt(y)) => {
if x < 0 {
Ordering::Less
} else {
(x as u64).cmp(&y)
}
}
(UInt(x), Int(y)) => {
if y < 0 {
Ordering::Greater
} else {
x.cmp(&(y as u64))
}
}
(Int(x), Float(y)) => cmp_i64_f64(x, y),
(Float(x), Int(y)) => cmp_i64_f64(y, x).reverse(),
(UInt(x), Float(y)) => cmp_u64_f64(x, y),
(Float(x), UInt(y)) => cmp_u64_f64(y, x).reverse(),
(Float(x), Float(y)) => x.partial_cmp(&y).unwrap(),
(
Decimal { mantissa, exp },
Decimal {
mantissa: m2,
exp: e2,
},
) => cmp_decimal_decimal(mantissa, exp, m2, e2),
(Decimal { mantissa, exp }, Int(y)) => cmp_decimal_int(mantissa, exp, i128::from(y)),
(Int(x), Decimal { mantissa, exp }) => {
cmp_decimal_int(mantissa, exp, i128::from(x)).reverse()
}
(Decimal { mantissa, exp }, UInt(y)) => cmp_decimal_int(mantissa, exp, i128::from(y)),
(UInt(x), Decimal { mantissa, exp }) => {
cmp_decimal_int(mantissa, exp, i128::from(x)).reverse()
}
(Decimal { mantissa, exp }, Float(y)) => cmp_decimal_f64(mantissa, exp, y),
(Float(x), Decimal { mantissa, exp }) => cmp_decimal_f64(mantissa, exp, x).reverse(),
(Big(x), Big(y)) => x.cmp(y),
(Big(x), _) => {
let y = other.to_owned_big();
x.cmp(y.as_ref())
}
(_, Big(y)) => {
let x = self.to_owned_big();
x.as_ref().cmp(y)
}
}
}
pub(crate) fn exact_json(self, has_decimal_point: bool) -> Option<String> {
if matches!(self.0, Repr::Int(0)) {
return None;
}
match self.0 {
Repr::Int(_) | Repr::UInt(_) if !has_decimal_point => None,
_ => {
let exact = self.to_owned_big();
let mut magnitude = exact.magnitude;
let mut exp = i64::from(exact.exp);
while !bigint::is_zero(&magnitude)
&& bigint::rem_small(&magnitude, bigint::TEN) == 0
{
bigint::div_small(&mut magnitude, bigint::TEN);
exp += 1;
}
Some(render_decimal(
exact.negative,
&bigint::to_decimal_digits(&magnitude),
exp as i32,
has_decimal_point,
))
}
}
}
fn to_owned_big(self) -> OwnedBigDec {
let (negative, magnitude, exp) = match self.0 {
Repr::Int(x) => (x < 0, bigint::from_u64(x.unsigned_abs()), 0),
Repr::UInt(x) => (false, bigint::from_u64(x), 0),
Repr::Decimal { mantissa, exp } => {
(mantissa < 0, bigint::from_u64(mantissa.unsigned_abs()), exp)
}
Repr::Float(x) => {
let (frac, exp2) = f64_frac_exp(x.abs());
let mut magnitude = bigint::from_u64(frac);
let exp = if exp2 >= 0 {
bigint::shl(&mut magnitude, exp2 as u64);
0
} else {
bigint::mul_pow5(&mut magnitude, (-exp2) as u64);
exp2
};
(x < 0.0, magnitude, exp)
}
Repr::Big(b) => (b.negative, b.magnitude.to_vec(), b.exp),
};
OwnedBigDec {
negative,
magnitude,
exp,
}
}
}
impl BigDec<'_> {
fn cmp(self, other: BigDec<'_>) -> Ordering {
match (
bigint::is_zero(self.magnitude),
bigint::is_zero(other.magnitude),
) {
(true, true) => return Ordering::Equal,
(true, false) if other.negative => return Ordering::Greater,
(true, false) => return Ordering::Less,
(false, true) if self.negative => return Ordering::Less,
(false, true) => return Ordering::Greater,
(false, false) => {}
}
match (self.negative, other.negative) {
(false, true) => return Ordering::Greater,
(true, false) => return Ordering::Less,
_ => {}
}
let ord = cmp_magnitude(self.magnitude, self.exp, other.magnitude, other.exp);
if self.negative {
ord.reverse()
} else {
ord
}
}
fn to_f64_lossy(self) -> f64 {
self.to_exponential()
.parse()
.expect("`to_exponential` emits a well-formed decimal literal")
}
fn to_exponential(self) -> String {
let digits = bigint::to_decimal_digits(self.magnitude);
let mut s = String::with_capacity(digits.len() + 13);
if self.negative {
s.push('-');
}
s.push_str(std::str::from_utf8(&digits).expect("ASCII digits"));
s.push('e');
s.push_str(&self.exp.to_string());
s
}
}
impl Debug for NumVal<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(x) = self.to_i64() {
Debug::fmt(&x, f)
} else if let Some(x) = self.to_u64() {
Debug::fmt(&x, f)
} else if let Repr::Big(b) = self.0 {
f.write_str(&b.to_exponential())
} else {
Debug::fmt(&self.to_f64_lossy(), f)
}
}
}
fn render_decimal(negative: bool, digits: &[u8], exp: i32, has_decimal_point: bool) -> String {
debug_assert!(!digits.is_empty() && digits[0] != b'0');
let digits = std::str::from_utf8(digits).expect("ASCII digits");
let n = digits.len() as i64;
let point = n + i64::from(exp);
let mut s = String::new();
if negative {
s.push('-');
}
if !has_decimal_point {
debug_assert!(exp >= 0, "an integer has a non-negative exponent");
s.push_str(digits);
s.extend(std::iter::repeat_n('0', exp as usize));
return s;
}
const MAX_LEADING_ZEROS: i64 = 6;
const MAX_TRAILING_ZEROS: i64 = 20;
if point <= -MAX_LEADING_ZEROS || point > n + MAX_TRAILING_ZEROS {
s.push_str(&digits[..1]);
if n > 1 {
s.push('.');
s.push_str(&digits[1..]);
}
s.push('e');
s.push_str(&(point - 1).to_string());
} else if point <= 0 {
s.push_str("0.");
s.extend(std::iter::repeat_n('0', (-point) as usize));
s.push_str(digits);
} else if point >= n {
s.push_str(digits);
s.extend(std::iter::repeat_n('0', (point - n) as usize));
s.push_str(".0");
} else {
let (whole, fraction) = digits.split_at(point as usize);
s.push_str(whole);
s.push('.');
s.push_str(fraction);
}
s
}
pub(crate) fn canonicalise(negative: bool, digits: &[u8], exp: i32) -> Canonical {
let mut magnitude = bigint::from_decimal_digits(digits);
if bigint::is_zero(&magnitude) {
return Canonical {
negative: false,
magnitude,
exp: 0,
small: Some(NumVal::from_i64(0)),
};
}
let mut exp = i64::from(exp);
while bigint::rem_small(&magnitude, bigint::TEN) == 0 {
bigint::div_small(&mut magnitude, bigint::TEN);
exp += 1;
}
let exp = i32::try_from(exp).expect("the parser bounds `exp + digits.len()` to an `i32`");
let small = reduce_fixed(negative, &magnitude, exp);
Canonical {
negative,
magnitude,
exp,
small,
}
}
fn reduce_fixed(negative: bool, magnitude: &[u64], exp: i32) -> Option<NumVal<'static>> {
if exp >= 0 {
if let Some(n) = integer_value(magnitude, exp) {
if !negative {
return Some(NumVal::from_u64(n));
}
return i64::try_from(-i128::from(n)).ok().map(NumVal::from_i64);
}
}
if let [mantissa] = *magnitude {
let signed = if negative {
i64::try_from(-i128::from(mantissa)).ok()
} else {
i64::try_from(mantissa).ok()
};
if let Some(m) = signed.filter(|&m| fits_decimal(m, i64::from(exp))) {
return Some(NumVal::from_decimal(m, exp));
}
}
exact_f64(negative, magnitude, exp).map(NumVal::from_f64)
}
fn exact_f64(negative: bool, magnitude: &[u64], exp: i32) -> Option<f64> {
if exp >= 0 {
if exp > 22 || bigint::significant_bits(magnitude) > 53 {
return None;
}
} else {
let k = (-i64::from(exp)).min(POW5_PROBE_EXP) as u32;
if bigint::rem_small(magnitude, bigint::pow5(k)) != 0 {
return None;
}
}
let big = BigDec {
negative,
magnitude,
exp,
};
let f = big.to_f64_lossy();
(f.is_finite() && NumVal(Repr::Big(big)).cmp(NumVal::from_f64(f)) == Ordering::Equal)
.then_some(f)
}
const POW5_PROBE_EXP: i64 = 27;
fn integer_value(magnitude: &[u64], exp: i32) -> Option<u64> {
debug_assert!(exp >= 0);
let mantissa = match *magnitude {
[] => return Some(0),
[m] => m,
_ => return None,
};
mantissa.checked_mul(10u64.checked_pow(u32::try_from(exp).ok()?)?)
}
const I64_RANGE: f64 = 9_223_372_036_854_775_808.0;
const U64_RANGE: f64 = 18_446_744_073_709_551_616.0;
fn can_represent_as_f64(x: u64) -> bool {
x.leading_zeros() + x.trailing_zeros() >= 11
}
fn cmp_by_fraction(b: f64, bt: f64) -> Ordering {
if b == bt {
Ordering::Equal
} else if b > bt {
Ordering::Less } else {
Ordering::Greater
}
}
fn cmp_i64_f64(a: i64, b: f64) -> Ordering {
if b >= I64_RANGE {
return Ordering::Less; }
if b < -I64_RANGE {
return Ordering::Greater; }
let bt = b.trunc(); match a.cmp(&(bt as i64)) {
Ordering::Equal => cmp_by_fraction(b, bt),
ord => ord,
}
}
fn cmp_u64_f64(a: u64, b: f64) -> Ordering {
if b < 0.0 {
return Ordering::Greater; }
if b >= U64_RANGE {
return Ordering::Less; }
let bt = b.trunc(); match a.cmp(&(bt as u64)) {
Ordering::Equal => cmp_by_fraction(b, bt),
ord => ord,
}
}
const DIVISORS: usize = 8;
fn pow10_u128(n: u32) -> NonZeroU128 {
const TABLE: [NonZeroU128; DIVISORS] = {
let mut table = [NonZeroU128::MIN; DIVISORS];
let mut i = 0;
while i < DIVISORS {
table[i] = match NonZeroU128::new(10u128.pow(i as u32)) {
Some(d) => d,
None => panic!("a power of ten is non-zero"),
};
i += 1;
}
table
};
TABLE[n as usize]
}
pub(crate) const DECIMAL_MIN_EXP: i64 = -7;
pub(crate) const DECIMAL_MAX_MAGNITUDE: i64 = 26;
pub(crate) fn fits_decimal(mantissa: i64, exp: i64) -> bool {
exp >= DECIMAL_MIN_EXP
&& i64::from(decimal_digits(mantissa.unsigned_abs())) + exp <= DECIMAL_MAX_MAGNITUDE
}
fn decimal_digits(mut x: u64) -> u32 {
let mut digits = 1;
while x >= 10 {
x /= 10;
digits += 1;
}
digits
}
fn decimal_digit_bounds(magnitude: &[u64]) -> (i64, i64) {
debug_assert!(!bigint::is_zero(magnitude));
let bits = bigint::bit_len(magnitude);
let lo = ((bits - 1) as f64 * std::f64::consts::LOG10_2).floor() as i64;
let hi = (bits as f64 * std::f64::consts::LOG10_2).floor() as i64 + 2;
(lo, hi)
}
fn cmp_magnitude(a: &[u64], ea: i32, b: &[u64], eb: i32) -> Ordering {
let (a_lo, a_hi) = decimal_digit_bounds(a);
let (b_lo, b_hi) = decimal_digit_bounds(b);
let (ea, eb) = (i64::from(ea), i64::from(eb));
if a_hi + ea < b_lo + eb {
return Ordering::Less;
}
if a_lo + ea > b_hi + eb {
return Ordering::Greater;
}
let common = ea.min(eb);
let mut sa = a.to_vec();
let mut sb = b.to_vec();
bigint::mul_pow10(&mut sa, (ea - common) as u64);
bigint::mul_pow10(&mut sb, (eb - common) as u64);
bigint::cmp(&sa, &sb)
}
fn decimal_int_value(mantissa: i64, exp: i32) -> Option<i128> {
if exp >= 0 {
Some(i128::from(mantissa) * 10i128.pow(exp as u32))
} else {
let div = pow10_u128((-exp) as u32);
let magnitude = i128::from(mantissa).unsigned_abs();
(magnitude % div == 0).then(|| {
let quotient = (magnitude / div) as i128;
if mantissa < 0 {
-quotient
} else {
quotient
}
})
}
}
pub(crate) fn decimal_to_f64_exact(mantissa: i64, exp: i32) -> Option<f64> {
if mantissa == 0 {
return Some(0.0);
}
if exp >= 0 {
let v = decimal_int_value(mantissa, exp)?;
i128_fits_f64(v).then_some(v as f64)
} else {
let k = (-exp) as u32;
let p5 = bigint::pow5(k);
let magnitude = mantissa.unsigned_abs();
if magnitude % p5 != 0 {
return None;
}
let num = i64::try_from(magnitude / p5).ok()?;
let num = if mantissa < 0 { -num } else { num };
i64_fits_f64(num).then_some(num as f64 / (1u64 << k) as f64)
}
}
fn i64_fits_f64(v: i64) -> bool {
v == 0 || 64 - v.unsigned_abs().leading_zeros() - v.unsigned_abs().trailing_zeros() <= 53
}
fn i128_fits_f64(v: i128) -> bool {
v == 0 || 128 - v.unsigned_abs().leading_zeros() - v.unsigned_abs().trailing_zeros() <= 53
}
#[inline]
pub(crate) fn decimal_to_f64_lossy(mantissa: i64, exp: i32) -> f64 {
if exp >= 0 {
return (i128::from(mantissa) * 10i128.pow(exp as u32)) as f64;
}
let m_abs = mantissa.unsigned_abs();
let denom = pow10_u128((-exp) as u32);
let v = if m_abs < (1 << 53) {
m_abs as f64 / denom.get() as f64
} else {
let scaled = u128::from(m_abs) << 64;
let mut q = scaled / denom;
if scaled % denom != 0 {
q |= 1;
}
q as f64 / 18_446_744_073_709_551_616.0 };
if mantissa < 0 {
-v
} else {
v
}
}
fn cmp_decimal_int(mantissa: i64, exp: i32, n: i128) -> Ordering {
if exp >= 0 {
(i128::from(mantissa) * 10i128.pow(exp as u32)).cmp(&n)
} else {
i128::from(mantissa).cmp(&(n * 10i128.pow((-exp) as u32)))
}
}
fn cmp_decimal_decimal(m1: i64, e1: i32, m2: i64, e2: i32) -> Ordering {
let de = e1 - e2;
if de >= 0 {
(i128::from(m1) * 10i128.pow(de as u32)).cmp(&i128::from(m2))
} else {
i128::from(m1).cmp(&(i128::from(m2) * 10i128.pow((-de) as u32)))
}
}
fn canonical_decimal(mut mantissa: i64, mut exp: i32) -> (i64, i32) {
if mantissa == 0 {
return (0, 0);
}
while mantissa % 10 == 0 {
mantissa /= 10;
exp += 1;
}
(mantissa, exp)
}
fn shl_u128(x: u128, n: u32) -> Option<u128> {
if x == 0 {
Some(0)
} else if n <= x.leading_zeros() {
Some(x << n)
} else {
None
}
}
fn f64_frac_exp(v: f64) -> (u64, i32) {
let bits = v.to_bits();
let raw_exp = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & 0x000f_ffff_ffff_ffff;
if raw_exp == 0 {
(frac, -1074) } else {
(frac | 0x0010_0000_0000_0000, raw_exp - 1075)
}
}
fn cmp_u128_f64(a: u128, b: f64) -> Ordering {
const U128_RANGE: f64 = 340_282_366_920_938_463_463_374_607_431_768_211_456.0; if b >= U128_RANGE {
return Ordering::Less; }
let bt = b.trunc(); match a.cmp(&(bt as u128)) {
Ordering::Equal => cmp_by_fraction(b, bt),
ord => ord,
}
}
fn cmp_decimal_magnitude(m_abs: u64, exp: i32, v: f64) -> Ordering {
if exp >= 0 {
cmp_u128_f64(u128::from(m_abs) * 10u128.pow(exp as u32), v)
} else {
let k = (-exp) as u32;
let (frac, fe) = f64_frac_exp(v);
let p = u128::from(frac) * 5u128.pow(k); let s = fe + k as i32;
if s >= 0 {
match shl_u128(p, s as u32) {
Some(rhs) => u128::from(m_abs).cmp(&rhs),
None => Ordering::Less, }
} else {
match shl_u128(u128::from(m_abs), (-s) as u32) {
Some(lhs) => lhs.cmp(&p),
None => Ordering::Greater, }
}
}
}
fn cmp_decimal_f64(mantissa: i64, exp: i32, v: f64) -> Ordering {
let d_neg = mantissa < 0;
if v == 0.0 {
return if d_neg {
Ordering::Less
} else {
Ordering::Greater
};
}
if d_neg != (v < 0.0) {
return if d_neg {
Ordering::Less
} else {
Ordering::Greater
};
}
let ord = cmp_decimal_magnitude(mantissa.unsigned_abs(), exp, v.abs());
if d_neg {
ord.reverse()
} else {
ord
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::hash_map::DefaultHasher;
fn hash_of(nv: NumVal<'_>) -> u64 {
let mut h = DefaultHasher::new();
nv.hash(&mut h);
h.finish()
}
type Lit = (bool, &'static str, i32);
fn num_val<'a>(
storage: &'a mut Option<OwnedBigDec>,
(negative, digits, exp): Lit,
) -> NumVal<'a> {
let c = canonicalise(negative, digits.as_bytes(), exp);
if let Some(nv) = c.small {
return nv;
}
let owned = storage.insert(OwnedBigDec {
negative: c.negative,
magnitude: c.magnitude,
exp: c.exp,
});
NumVal::from_big(owned.negative, &owned.magnitude, owned.exp)
}
fn small(lit: Lit) -> NumVal<'static> {
canonicalise(lit.0, lit.1.as_bytes(), lit.2)
.small
.unwrap_or_else(|| panic!("{}e{} unexpectedly needs arbitrary precision", lit.1, lit.2))
}
fn assert_big(lit: Lit) {
assert!(
canonicalise(lit.0, lit.1.as_bytes(), lit.2).small.is_none(),
"{}e{} should need arbitrary precision",
lit.1,
lit.2
);
}
fn cmp_lit(a: Lit, b: Lit) -> Ordering {
let (mut sa, mut sb) = (None, None);
num_val(&mut sa, a).cmp(num_val(&mut sb, b))
}
fn hash_lit(lit: Lit) -> u64 {
let mut storage = None;
hash_of(num_val(&mut storage, lit))
}
#[test]
fn from_f64_normalises_integers_at_the_i64_u64_boundaries() {
let two63 = 9_223_372_036_854_775_808.0_f64;
let two64 = 18_446_744_073_709_551_616.0_f64;
assert_eq!(
NumVal::from_f64(two63 - 1024.0).to_i64(),
Some(i64::MAX - 1023)
);
assert_eq!(NumVal::from_f64(two63).to_i64(), None);
assert_eq!(NumVal::from_f64(two63).to_u64(), Some(1 << 63));
assert_eq!(NumVal::from_f64(-two63).to_i64(), Some(i64::MIN));
assert_eq!(
NumVal::from_f64(two64 - 2048.0).to_u64(),
Some(u64::MAX - 2047)
);
assert_eq!(NumVal::from_f64(two64).to_u64(), None);
assert_eq!(NumVal::from_f64(two64).to_f64(), Some(two64));
}
#[test]
fn decimal_extracts_integers_but_not_fractions() {
let tenth = NumVal::from_decimal(1, -1);
assert_eq!(tenth.to_i64(), None);
assert_eq!(tenth.to_u64(), None);
assert_eq!(tenth.to_f64(), None);
assert_eq!(tenth.to_f64_lossy(), 0.1);
let two = NumVal::from_decimal(20, -1);
assert_eq!(two.to_i64(), Some(2));
assert_eq!(two.to_u64(), Some(2));
}
#[test]
fn decimal_compares_exactly_against_decimals_and_integers() {
let tenth = NumVal::from_decimal(1, -1); let three_tenths = NumVal::from_decimal(3, -1); let tenth_scaled = NumVal::from_decimal(10, -2); assert_eq!(tenth.cmp(three_tenths), Ordering::Less);
assert_eq!(tenth.cmp(tenth_scaled), Ordering::Equal);
assert_eq!(tenth.cmp(NumVal::from_i64(0)), Ordering::Greater);
assert_eq!(tenth.cmp(NumVal::from_i64(1)), Ordering::Less);
let two = NumVal::from_decimal(20, -1);
assert_eq!(two.cmp(NumVal::from_i64(2)), Ordering::Equal);
assert_eq!(two.cmp(NumVal::from_u64(2)), Ordering::Equal);
assert_eq!(NumVal::from_i64(2).cmp(two), Ordering::Equal);
}
#[test]
fn decimal_hash_stays_consistent_with_equality() {
let two_dec = NumVal::from_decimal(20, -1);
assert_eq!(hash_of(two_dec), hash_of(NumVal::from_i64(2)));
assert_eq!(hash_of(two_dec), hash_of(NumVal::from_u64(2)));
let a = NumVal::from_decimal(1, -1);
let b = NumVal::from_decimal(10, -2);
assert_eq!(hash_of(a), hash_of(b));
assert_ne!(a.cmp(NumVal::from_f64(0.1)), Ordering::Equal);
assert_eq!(a.cmp(NumVal::from_f64(0.1)), Ordering::Less);
}
#[test]
fn canonicalise_reduces_to_the_smallest_variant() {
assert_eq!(small((false, "0", 0)).to_i64(), Some(0));
assert_eq!(small((false, "0000", 5)).to_i64(), Some(0));
assert_eq!(small((true, "0", -3)).to_i64(), Some(0));
assert_eq!(small((false, "123", 0)).to_i64(), Some(123));
assert_eq!(small((true, "123", 0)).to_i64(), Some(-123));
assert_eq!(small((false, "12300", -2)).to_i64(), Some(123));
assert_eq!(small((false, "123", 2)).to_i64(), Some(12300));
assert_eq!(
small((true, "9223372036854775808", 0)).to_i64(),
Some(i64::MIN)
);
assert_eq!(small((false, "9223372036854775808", 0)).to_i64(), None);
assert_eq!(
small((false, "9223372036854775808", 0)).to_u64(),
Some(1 << 63)
);
assert_eq!(
small((false, "18446744073709551615", 0)).to_u64(),
Some(u64::MAX)
);
assert_eq!(
small((false, "18446744073709551616", 0)).to_f64(),
Some(18_446_744_073_709_551_616.0)
);
assert_eq!(small((false, "390625", -8)).to_f64(), Some(0.003_906_25));
let tenth = small((false, "1", -1));
assert_eq!(tenth.to_i64(), None);
assert_eq!(tenth.to_f64(), None);
assert_eq!(tenth.to_f64_lossy(), 0.1);
}
#[test]
fn canonicalise_spills_only_what_no_fixed_width_variant_holds() {
assert_big((false, "123456789012345678901234567890123", 0));
assert_big((true, "123456789012345678901234567890123", 0));
assert_big((false, "1234567890123456789012345", -25));
assert_big((false, "1", -30));
assert_big((false, "7", 40));
assert_big((false, "3", 1_000_000));
assert_big((false, "3", -1_000_000));
}
#[test]
fn big_orders_exactly_against_every_other_variant() {
let big = (false, "123456789012345678901234567890123", 0); assert_eq!(cmp_lit(big, (false, "1", 0)), Ordering::Greater);
assert_eq!(cmp_lit((false, "1", 0), big), Ordering::Less);
assert_eq!(cmp_lit(big, (true, "1", 0)), Ordering::Greater);
assert_eq!(cmp_lit(big, (false, "1", 33)), Ordering::Less); assert_eq!(cmp_lit(big, (false, "1", 32)), Ordering::Greater); assert_eq!(cmp_lit(big, (false, "1", -1)), Ordering::Greater);
assert_eq!(
cmp_lit((true, "9", 1_000_000), (false, "1", -1_000_000)),
Ordering::Less
);
let a = (false, "1234567890123456789012345", -25); let b = (false, "1234567890123456789012346", -25); assert_eq!(cmp_lit(a, b), Ordering::Less);
assert_eq!(cmp_lit(b, a), Ordering::Greater);
assert_eq!(cmp_lit(a, a), Ordering::Equal);
assert_eq!(
cmp_lit((true, a.1, a.2), (true, b.1, b.2)),
Ordering::Greater
);
}
#[test]
fn big_is_never_equal_to_a_fixed_width_variant() {
for &lit in &[
(false, "1234567890123456789012345", -25),
(false, "123456789012345678901234567890123", 0),
(false, "1", -30),
] {
let (mut sa, mut sb) = (None, None);
let a = num_val(&mut sa, lit);
assert_eq!(a.to_i64(), None);
assert_eq!(a.to_u64(), None);
assert_eq!(a.to_f64(), None);
let nearest = num_val(&mut sb, lit).to_f64_lossy();
assert_ne!(a.cmp(NumVal::from_f64(nearest)), Ordering::Equal);
}
}
#[test]
fn equal_numbers_hash_alike_however_they_are_written() {
let groups: &[&[Lit]] = &[
&[(false, "0", 0), (false, "000", 3), (true, "0", -3)],
&[
(false, "123", 0),
(false, "1230", -1),
(false, "123000", -3),
],
&[(false, "1", -1), (false, "10", -2), (false, "100000", -6)],
&[
(false, "18446744073709551616", 0),
(false, "18446744073709551616000", -3),
],
&[
(false, "1234567890123456789012345", -25),
(false, "12345678901234567890123450", -26),
(false, "1234567890123456789012345000", -28),
],
&[
(false, "123456789012345678901234567890123", 0),
(false, "123456789012345678901234567890123000", -3),
(false, "12345678901234567890123456789012300", -2),
],
];
for group in groups {
for &a in *group {
for &b in *group {
assert_eq!(
cmp_lit(a, b),
Ordering::Equal,
"{}e{} != {}e{}",
a.1,
a.2,
b.1,
b.2
);
assert_eq!(
hash_lit(a),
hash_lit(b),
"{}e{} and {}e{} are equal but hash differently",
a.1,
a.2,
b.1,
b.2
);
}
}
}
}
#[test]
fn big_orders_as_a_total_order() {
let ascending: &[Lit] = &[
(true, "1", 0), (false, "0", 0), (false, "3", -1_000_000), (false, "1", -30), (false, "1", -1), (false, "1234567890123456789012345", -25), (false, "5", -1), (false, "1", 0), (false, "18446744073709551616", 0), (false, "123456789012345678901234567890123", 0), (false, "3", 1_000_000), ];
for (i, &a) in ascending.iter().enumerate() {
for &b in &ascending[i + 1..] {
assert_eq!(
cmp_lit(a, b),
Ordering::Less,
"{}e{} should be less than {}e{}",
a.1,
a.2,
b.1,
b.2
);
assert_eq!(
cmp_lit(b, a),
Ordering::Greater,
"{}e{} vs {}e{} is not antisymmetric",
a.1,
a.2,
b.1,
b.2
);
}
}
}
#[test]
fn big_to_f64_lossy_is_correctly_rounded() {
for &(digits, exp) in &[
("1234567890123456789012345", -25),
("123456789012345678901234567890123", 0),
("10000000000000000001", -1),
("99999999999999999999999999999999", -16),
("1", -30),
("7", 40),
] {
let mut storage = None;
let nv = num_val(&mut storage, (false, digits, exp));
let oracle: f64 = format!("{}e{}", digits, exp).parse().unwrap();
assert_eq!(nv.to_f64_lossy(), oracle, "{}e{}", digits, exp);
let mut storage = None;
let nv = num_val(&mut storage, (true, digits, exp));
assert_eq!(nv.to_f64_lossy(), -oracle, "-{}e{}", digits, exp);
}
}
#[test]
fn decimal_to_f64_lossy_is_correctly_rounded() {
for &(m, e) in &[
(1_i64, -1), (3, -1), (9492881567496375, -1), (12345678901234567, -1), (99999999999999999, -7), (-12345678901234567, -3), (36028797018963967, -1), (36028797018963967, 7), (7, -7), ] {
let oracle: f64 = format!("{}e{}", m, e).parse().unwrap();
assert_eq!(decimal_to_f64_lossy(m, e), oracle, "{} e {}", m, e);
}
}
}