#![allow(
clippy::unreadable_literal,
clippy::excessive_precision,
clippy::approx_constant
)]
use std::f64;
pub(crate) fn i64_cases() -> Vec<i64> {
let mut v = vec![
0,
1,
-1,
2,
-2,
5,
-5,
9,
10,
-10,
11,
-11,
99,
100,
-100,
101,
127,
128,
-128,
-129,
255,
256,
1000,
-1000,
1_000_000,
-1_000_000,
i32::MAX as i64,
i32::MIN as i64,
i32::MAX as i64 + 1,
i32::MIN as i64 - 1,
u32::MAX as i64,
u32::MAX as i64 + 1,
1i64 << 31,
-(1i64 << 31),
(1i64 << 53) - 1,
1i64 << 53,
(1i64 << 53) + 1,
(1i64 << 53) + 2,
-((1i64 << 53) + 1),
1i64 << 52,
1i64 << 54,
(1i64 << 23) - 1,
1i64 << 23,
(1i64 << 23) + 1,
-(1i64 << 23),
-(1i64 << 23) - 1,
(1i64 << 55) - 1,
1i64 << 55,
(1i64 << 55) + 1,
-(1i64 << 55),
-(1i64 << 55) - 1,
-(1i64 << 55) + 1,
i64::MIN,
i64::MIN + 1,
i64::MAX,
i64::MAX - 1,
9_007_199_254_740_881,
9_999_999_999_999_937,
9_223_372_036_854_775_783,
6_148_914_691_236_517_205, -6_148_914_691_236_517_205,
36_000_000_000_000_000,
-36_000_000_000_000_000,
];
let mut p: i64 = 1;
for _ in 0..=18 {
v.push(p);
v.push(-p);
p = p.saturating_mul(10);
}
v
}
pub(crate) fn u64_cases() -> Vec<u64> {
let mut v = vec![
0,
1,
2,
u32::MAX as u64,
u32::MAX as u64 + 1,
(1u64 << 53) - 1,
1u64 << 53,
(1u64 << 53) + 1,
(1u64 << 55) - 1,
1u64 << 55,
(1u64 << 55) + 1,
i64::MAX as u64,
i64::MAX as u64 + 1, (1u64 << 63) - 1,
1u64 << 63,
(1u64 << 63) + 1,
u64::MAX,
u64::MAX - 1,
18_446_744_073_709_551_557, 12_297_829_382_473_034_410, 6_148_914_691_236_517_205, ];
let mut p: u64 = 1;
for _ in 0..=19 {
v.push(p);
p = p.saturating_mul(10);
}
v
}
pub(crate) fn f64_cases() -> Vec<f64> {
let mut v = vec![
0.0,
-0.0,
1.0,
-1.0,
2.0,
-2.0,
10.0,
100.0,
-100.0,
0.5,
-0.5,
0.25,
0.75,
0.125,
0.375,
0.0625,
0.1875,
2.5,
63.5,
-63.5,
0.0078125, -0.0078125, 0.00390625, 949288156749637.5,
-949288156749637.5,
3000000000000000.5, 123456789012345.75, 0.1,
0.2,
0.3,
0.7,
1.1,
3.14,
0.30000000000000004, f64::consts::PI,
f64::consts::E,
f64::consts::TAU,
f64::consts::SQRT_2,
-f64::consts::PI,
1e7,
1e8,
1e15,
1e16,
1e17,
1e18,
1e19,
1e20,
1e21,
1e22, 1e23, -1e18,
(1u64 << 52) as f64,
(1u64 << 53) as f64,
(1u64 << 54) as f64,
(1u64 << 55) as f64,
16777216.0, 16777217.0, 16777218.0,
9223372036854774784.0, 9223372036854775808.0, 18446744073709549568.0, 18446744073709551616.0, 1.7014118346046923e38, f64::MAX,
f64::MIN, f64::MIN_POSITIVE,
f64::from_bits(1), -f64::from_bits(1),
f64::EPSILON,
];
let extra: Vec<f64> = [1e15, 1e16, 1e17, 1e19, 1e20, 1e22, 1e23, f64::MAX]
.iter()
.map(|x| -x)
.collect();
v.extend(extra);
v
}
pub(crate) fn f64_nonfinite_cases() -> Vec<f64> {
vec![f64::NAN, -f64::NAN, f64::INFINITY, f64::NEG_INFINITY]
}
pub(crate) fn json_number_cases() -> Vec<&'static str> {
vec![
"0",
"-0",
"1",
"-1",
"10",
"-10",
"100",
"255",
"256",
"1000000000000000000",
"-1000000000000000000",
"9223372036854775807",
"9223372036854775808",
"18446744073709551615",
"18446744073709551616", "-9223372036854775808", "-9223372036854775809", "100000000000000000000", "-100000000000000000000",
"8388608", "8388607", "36028797018963968", "36028797018963967", "9007199254740992", "9007199254740993", "12345678901234567",
"9999999999999937",
"0.0",
"-0.0",
"1.0",
"2.0",
"1.5",
"-1.5",
"0.5",
"0.25",
"0.1",
"3.141592653589793",
"0.0078125", "0.00390625", "0.30000000000000004",
"1.50",
"2.000",
"100.00",
"9007199254740992.0", "1e0",
"0e0",
"1e1",
"2e2",
"1e18", "1E18",
"1e+18",
"1.5e3",
"1.5E-3",
"1e-7",
"1e7", "1e22", "1e23", "1e308",
"1e-308",
"5e-324", "1e-400", "1.7976931348623157e308", ]
}
pub(crate) fn string_number_cases() -> Vec<String> {
let mut v: Vec<String> = json_number_cases()
.iter()
.map(|s| (*s).to_owned())
.collect();
v.extend(i64_cases().iter().map(|x| x.to_string()));
v.extend(u64_cases().iter().map(|x| x.to_string()));
v.extend(
f64_cases()
.iter()
.map(|x| serde_json::to_string(x).expect("a finite f64 serialises")),
);
v
}
#[cfg(test)]
mod tests {
use super::*;
use crate::IValue;
use std::collections::hash_map::DefaultHasher;
use std::convert::TryFrom;
use std::hash::{Hash, Hasher};
fn f64_exact_u64(a: u64) -> bool {
a.leading_zeros() + a.trailing_zeros() >= 11
}
fn hash_of(v: &IValue) -> u64 {
let mut h = DefaultHasher::new();
v.hash(&mut h);
h.finish()
}
fn json(s: &str) -> IValue {
serde_json::from_str(s).unwrap_or_else(|e| panic!("parse {:?}: {}", s, e))
}
fn inum(s: &str) -> IValue {
IValue::from(
s.parse::<crate::INumber>()
.unwrap_or_else(|e| panic!("parse {:?}: {}", s, e)),
)
}
#[cfg(not(miri))]
fn number_pool() -> Vec<IValue> {
let mut pool = Vec::new();
pool.extend(i64_cases().into_iter().map(IValue::from));
pool.extend(u64_cases().into_iter().map(IValue::from));
pool.extend(f64_cases().into_iter().map(IValue::from));
pool.extend(string_number_cases().iter().map(|s| inum(s)));
pool
}
#[test]
fn deserialize_any_is_total_over_every_number() {
for s in string_number_cases() {
let v = inum(&s);
let back: serde_json::Value = crate::from_value(&v)
.unwrap_or_else(|e| panic!("deserialize_any on {:?}: {}", s, e));
assert!(back.is_number(), "{:?} did not come back a number", s);
}
}
#[test]
fn numbers_round_trip_through_json() {
let mut cases = string_number_cases();
cases.extend(
[
"123456789012345678901234567890123",
"-123456789012345678901234567890123",
"0.1234567890123456789012345678901234567890",
"1e400",
"-1e400",
"1e-400",
"100000000000000000000",
"18446744073709551616",
"1.2345678901234567891e19",
"1e19",
]
.map(str::to_owned),
);
for s in &cases {
let Ok(n) = s.parse::<crate::INumber>() else {
assert!(
!cfg!(feature = "arbitrary_precision"),
"{:?} should be representable",
s
);
continue;
};
let text = serde_json::to_string(&n).unwrap_or_else(|e| panic!("write {:?}: {}", s, e));
let back: crate::INumber = text.parse().unwrap_or_else(|e| {
panic!("{:?} wrote {:?}, which does not re-parse: {}", s, text, e)
});
assert_eq!(back, n, "{:?} wrote {:?}, a different value", s, text);
assert_eq!(
back.has_decimal_point(),
n.has_decimal_point(),
"{:?} wrote {:?}, which changed integer/float shape",
s,
text
);
assert_eq!(hash_of(&back.into()), hash_of(&n.into()), "{:?} hash", s);
}
}
#[test]
fn i64_inputs_are_consistent() {
for &x in &i64_cases() {
let v = IValue::from(x);
assert!(v.is_number(), "{} not a number", x);
assert_eq!(v.to_i64(), Some(x), "{} to_i64", x);
assert!(!v.as_number().unwrap().has_decimal_point(), "{} dot", x);
assert_eq!(v.to_u64(), u64::try_from(x).ok(), "{} to_u64", x);
assert_eq!(
v.to_f64().is_some(),
f64_exact_u64(x.unsigned_abs()),
"{} to_f64 exactness",
x
);
assert_eq!(v.to_i32(), i32::try_from(x).ok(), "{} to_i32", x);
assert_eq!(v.to_u32(), u32::try_from(x).ok(), "{} to_u32", x);
let s = serde_json::to_string(&v).unwrap();
let back: IValue = serde_json::from_str(&s).unwrap();
assert_eq!(v, back, "{} serde round-trip ({})", x, s);
}
}
#[test]
fn u64_inputs_are_consistent() {
for &x in &u64_cases() {
let v = IValue::from(x);
assert!(v.is_number(), "{} not a number", x);
assert_eq!(v.to_u64(), Some(x), "{} to_u64", x);
assert!(!v.as_number().unwrap().has_decimal_point(), "{} dot", x);
assert_eq!(v.to_i64(), i64::try_from(x).ok(), "{} to_i64", x);
assert_eq!(v.to_f64().is_some(), f64_exact_u64(x), "{} to_f64", x);
let s = serde_json::to_string(&v).unwrap();
let back: IValue = serde_json::from_str(&s).unwrap();
assert_eq!(v, back, "{} serde round-trip ({})", x, s);
}
}
#[test]
fn f64_inputs_are_consistent() {
for &x in &f64_cases() {
let v = IValue::from(x);
assert!(v.is_number(), "{} not a number", x);
assert!(v.as_number().unwrap().has_decimal_point(), "{} dot", x);
assert_eq!(v.to_f64_lossy(), Some(x), "{} to_f64_lossy", x);
if let Some(i) = v.to_i64() {
assert_eq!(i as f64, x, "{} to_i64 value", x);
}
if let Some(u) = v.to_u64() {
assert_eq!(u as f64, x, "{} to_u64 value", x);
}
let out = serde_json::to_string(&v).unwrap();
let back: IValue = serde_json::from_str(&out).unwrap();
#[cfg(feature = "arbitrary_precision")]
{
assert_eq!(back.to_f64_lossy(), Some(x), "{} exact serde round-trip", x);
}
#[cfg(not(feature = "arbitrary_precision"))]
{
let baseline: f64 =
serde_json::from_str(&serde_json::to_string(&x).unwrap()).unwrap();
assert_eq!(
back.to_f64_lossy(),
Some(baseline),
"{} serde round-trip",
x
);
}
}
}
#[test]
fn round_trips_through_inumber_exactly() {
for &x in &i64_cases() {
assert_eq!(crate::INumber::from(x).to_i64(), Some(x), "i64 {}", x);
}
for &x in &u64_cases() {
assert_eq!(crate::INumber::from(x).to_u64(), Some(x), "u64 {}", x);
}
for &x in &f64_cases() {
let n = crate::INumber::try_from(x).unwrap();
assert_eq!(n.to_f64(), Some(x), "f64 {} exact", x);
assert_eq!(n.to_f64_lossy(), x, "f64 {} lossy", x);
}
}
#[test]
fn nonfinite_f64_inputs_are_rejected() {
for &x in &f64_nonfinite_cases() {
assert!(crate::INumber::try_from(x).is_err(), "{} accepted", x);
assert!(IValue::from(x).is_null(), "{} not null", x);
}
}
#[test]
fn json_inputs_are_consistent() {
for &s in &json_number_cases() {
let v: IValue =
serde_json::from_str(s).unwrap_or_else(|e| panic!("parse {:?}: {}", s, e));
assert!(v.is_number(), "{:?} not a number", s);
let syntactic_float = s.bytes().any(|b| matches!(b, b'.' | b'e' | b'E'));
let fits_int = s.parse::<i64>().is_ok() || s.parse::<u64>().is_ok();
let expect_dot = if cfg!(feature = "arbitrary_precision") {
syntactic_float
} else {
syntactic_float || !fits_int || s == "-0"
};
assert_eq!(
v.as_number().unwrap().has_decimal_point(),
expect_dot,
"{:?} decimal-point",
s
);
let via_value: serde_json::Value = serde_json::from_str(s).unwrap();
assert_eq!(v, IValue::from(via_value), "{:?} vs serde Value", s);
let out = serde_json::to_string(&v).unwrap();
let back: IValue = serde_json::from_str(&out).unwrap();
let back_value: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(back, IValue::from(back_value), "{:?} reparse agreement", s);
}
}
#[test]
fn string_inputs_are_consistent() {
for s in &string_number_cases() {
let s = s.as_str();
let v = inum(s);
assert!(v.is_number(), "{:?} not a number", s);
let exact_big_integers = cfg!(feature = "arbitrary_precision");
let syntactic_float = s.bytes().any(|b| matches!(b, b'.' | b'e' | b'E'));
let fits_int = s.parse::<i64>().is_ok() || s.parse::<u64>().is_ok();
let expect_dot = syntactic_float || (!fits_int && !exact_big_integers);
let dot = v.as_number().unwrap().has_decimal_point();
assert_eq!(dot, expect_dot, "{:?} decimal-point", s);
if !expect_dot && fits_int {
let js = json(s);
assert_eq!(v, js, "{:?} vs serde_json", s);
if !js.as_number().unwrap().has_decimal_point() {
assert_eq!(v.number_repr_key(), js.number_repr_key(), "{:?} repr", s);
}
} else if expect_dot {
assert_eq!(
v.to_f64_lossy(),
Some(s.parse::<f64>().unwrap()),
"{:?} nearest f64",
s
);
}
let out = serde_json::to_string(&v).unwrap();
assert_eq!(
inum(&out).to_f64_lossy(),
v.to_f64_lossy(),
"{:?} round-trip ({})",
s,
out
);
}
}
#[test]
fn string_parsing_matches_direct_construction() {
for &x in &i64_cases() {
let v = inum(&x.to_string());
assert_eq!(v, IValue::from(x), "i64 {}", x);
assert!(!v.as_number().unwrap().has_decimal_point(), "i64 {} dot", x);
}
for &x in &u64_cases() {
let v = inum(&x.to_string());
assert_eq!(v, IValue::from(x), "u64 {}", x);
assert!(!v.as_number().unwrap().has_decimal_point(), "u64 {} dot", x);
}
for &x in &f64_cases() {
let v = inum(&serde_json::to_string(&x).unwrap());
assert_eq!(v.to_f64_lossy(), Some(x), "f64 {}", x);
assert!(v.as_number().unwrap().has_decimal_point(), "f64 {} dot", x);
}
}
#[test]
fn equal_magnitudes_agree_across_representations() {
let pairs = [
("100", "1e2"),
("10000000", "1e7"),
("1000000000000000000", "1e18"),
];
for (int_str, float_str) in pairs {
let int = json(int_str);
let float = json(float_str);
assert_eq!(int, float, "{} == {}", int_str, float_str);
assert!(!int.as_number().unwrap().has_decimal_point());
assert!(float.as_number().unwrap().has_decimal_point());
assert_eq!(
hash_of(&int),
hash_of(&float),
"{} hash {}",
int_str,
float_str
);
}
}
#[test]
fn normalisation_makes_equal_values_indistinguishable() {
let groups: Vec<Vec<IValue>> = vec![
vec![
IValue::from(0_i64),
IValue::from(0_u64),
IValue::from(0.0_f64),
IValue::from(-0.0_f64),
json("0"),
json("-0"),
json("0.0"),
json("-0.0"),
json("0e0"),
],
vec![
IValue::from(1_i64),
IValue::from(1_u64),
IValue::from(1.0_f64),
json("1"),
json("1.0"),
json("1e0"),
],
vec![
IValue::from(-1_i64),
IValue::from(-1.0_f64),
json("-1"),
json("-1.0"),
],
vec![
IValue::from(100_i64),
IValue::from(1e2_f64),
json("100"),
json("1e2"),
json("100.0"),
],
vec![
IValue::from(1_i64 << 55),
IValue::from(1_u64 << 55),
IValue::from((1_u64 << 55) as f64),
],
vec![
IValue::from(1_000_000_000_000_000_000_i64),
IValue::from(1e18_f64),
json("1000000000000000000"),
json("1e18"),
],
vec![
IValue::from(1_u64 << 63),
IValue::from(9223372036854775808.0_f64),
json("9223372036854775808"),
],
vec![IValue::from(0.5_f64), json("0.5"), json("5e-1")],
];
for g in &groups {
for a in g {
for b in g {
assert_eq!(a, b, "same group not equal: {:?} vs {:?}", a, b);
assert_eq!(
hash_of(a),
hash_of(b),
"same group hashes differ: {:?} vs {:?}",
a,
b
);
}
}
}
for (i, g1) in groups.iter().enumerate() {
for (j, g2) in groups.iter().enumerate() {
if i != j {
assert_ne!(
g1[0], g2[0],
"different groups equal: {:?} vs {:?}",
g1[0], g2[0]
);
}
}
}
}
#[test]
fn sorting_matches_reference_order() {
let ordered: Vec<IValue> = vec![
IValue::from(f64::MIN), IValue::from(-1e300_f64),
IValue::from(i64::MIN),
IValue::from(-1e18_f64),
IValue::from(-(1_i64 << 55)),
IValue::from(-1_000_000_i64),
IValue::from(-2.5_f64),
IValue::from(-1_i64),
IValue::from(-0.5_f64),
IValue::from(-0.0078125_f64),
IValue::from(0_i64),
IValue::from(0.0078125_f64),
IValue::from(0.5_f64),
IValue::from(1_i64),
IValue::from(1.5_f64),
IValue::from(2_i64),
IValue::from(100_i64),
IValue::from(9007199254740992.0_f64), IValue::from(9007199254740993_i64), IValue::from(1e17_f64),
IValue::from(1e18_f64),
IValue::from(i64::MAX), IValue::from(1_u64 << 63), IValue::from(u64::MAX), IValue::from(1e20_f64), IValue::from(1e300_f64),
IValue::from(f64::MAX),
];
for w in ordered.windows(2) {
assert!(
w[0] < w[1],
"not strictly increasing: {:?} !< {:?}",
w[0],
w[1]
);
}
let mut shuffled = ordered.clone();
shuffled.reverse();
shuffled.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(shuffled, ordered);
}
#[test]
fn representation_is_canonical() {
for &x in &i64_cases() {
let base = IValue::from(x).number_repr_key();
assert_eq!(
json(&x.to_string()).number_repr_key(),
base,
"int {} JSON",
x
);
if x >= 0 {
let via_u64 = IValue::from(x as u64).number_repr_key();
assert_eq!(via_u64, base, "int {} via u64", x);
}
}
for &x in &u64_cases() {
let base = IValue::from(x).number_repr_key();
assert_eq!(
json(&x.to_string()).number_repr_key(),
base,
"uint {} JSON",
x
);
if let Ok(i) = i64::try_from(x) {
assert_eq!(
IValue::from(i).number_repr_key(),
base,
"uint {} via i64",
x
);
}
}
let groups: Vec<Vec<IValue>> = vec![
vec![
IValue::from(0_i64),
IValue::from(0_u64),
IValue::from(0.0_f64),
IValue::from(-0.0_f64),
json("0"),
json("-0"),
json("0.0"),
json("-0.0"),
json("0e0"),
],
vec![
IValue::from(1_i64),
IValue::from(1.0_f64),
json("1"),
json("1.0"),
json("1e0"),
],
vec![
IValue::from(100_i64),
IValue::from(1e2_f64),
json("100"),
json("1e2"),
json("100.0"),
],
vec![
IValue::from(1_000_000_000_000_000_000_i64),
IValue::from(1e18_f64),
json("1000000000000000000"),
json("1e18"),
],
vec![IValue::from(0.5_f64), json("0.5"), json("5e-1")],
];
for g in &groups {
for a in g {
for b in g {
if a.as_number().unwrap().has_decimal_point()
== b.as_number().unwrap().has_decimal_point()
{
assert_eq!(
a.number_repr_key(),
b.number_repr_key(),
"same value & decimal-point, different representation: {:?} vs {:?}",
a,
b
);
}
}
}
}
}
#[cfg(not(miri))]
#[test]
fn equal_values_share_hash_and_representation() {
let pool = number_pool();
for a in &pool {
for b in &pool {
if a != b {
continue;
}
assert_eq!(
hash_of(a),
hash_of(b),
"equal values hash differently: {:?} vs {:?}",
a,
b
);
if a.as_number().unwrap().has_decimal_point()
== b.as_number().unwrap().has_decimal_point()
{
assert_eq!(
a.number_repr_key(),
b.number_repr_key(),
"equal values, same decimal-point, different representation: {:?} vs {:?}",
a,
b
);
}
}
}
}
#[cfg(not(miri))]
#[test]
fn comparison_is_a_consistent_total_order() {
use std::cmp::Ordering;
let pool = number_pool();
for a in &pool {
for b in &pool {
let ab = a
.partial_cmp(b)
.unwrap_or_else(|| panic!("no ordering: {:?} vs {:?}", a, b));
let ba = b
.partial_cmp(a)
.unwrap_or_else(|| panic!("no ordering: {:?} vs {:?}", b, a));
assert_eq!(ab, ba.reverse(), "antisymmetry: {:?} vs {:?}", a, b);
assert_eq!(
a == b,
ab == Ordering::Equal,
"eq/cmp disagree: {:?} vs {:?}",
a,
b
);
}
}
}
}