#![allow(clippy::float_cmp)]
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::str::FromStr;
use crate::value::IValue;
#[repr(transparent)]
#[derive(Clone)]
pub struct INumber(pub(crate) IValue);
value_subtype_impls!(INumber, into_number, as_number, as_number_mut);
impl INumber {
#[must_use]
pub fn zero() -> Self {
INumber(IValue::new_i64(0))
}
#[must_use]
pub fn one() -> Self {
INumber(IValue::new_i64(1))
}
#[must_use]
pub fn to_i64(&self) -> Option<i64> {
self.0.to_i64()
}
#[must_use]
pub fn to_u64(&self) -> Option<u64> {
self.0.to_u64()
}
#[must_use]
pub fn to_f64(&self) -> Option<f64> {
self.0.to_f64()
}
#[must_use]
pub fn to_f32(&self) -> Option<f32> {
self.0.to_f32()
}
#[must_use]
pub fn to_i32(&self) -> Option<i32> {
self.0.to_i32()
}
#[must_use]
pub fn to_u32(&self) -> Option<u32> {
self.0.to_u32()
}
#[must_use]
pub fn to_isize(&self) -> Option<isize> {
self.0.to_isize()
}
#[must_use]
pub fn to_usize(&self) -> Option<usize> {
self.0.to_usize()
}
#[must_use]
pub fn to_f64_lossy(&self) -> f64 {
self.0.to_f64_lossy().unwrap()
}
#[must_use]
pub fn to_f32_lossy(&self) -> f32 {
self.0.to_f32_lossy().unwrap()
}
#[must_use]
pub fn has_decimal_point(&self) -> bool {
self.0.has_decimal_point()
}
}
impl Hash for INumber {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl From<u64> for INumber {
fn from(v: u64) -> Self {
INumber(IValue::new_u64(v))
}
}
impl From<u32> for INumber {
fn from(v: u32) -> Self {
INumber(IValue::new_u64(u64::from(v)))
}
}
impl From<u16> for INumber {
fn from(v: u16) -> Self {
INumber(IValue::new_u64(u64::from(v)))
}
}
impl From<u8> for INumber {
fn from(v: u8) -> Self {
INumber(IValue::new_u64(u64::from(v)))
}
}
impl From<usize> for INumber {
fn from(v: usize) -> Self {
INumber(IValue::new_u64(v as u64))
}
}
impl From<i64> for INumber {
fn from(v: i64) -> Self {
INumber(IValue::new_i64(v))
}
}
impl From<i32> for INumber {
fn from(v: i32) -> Self {
INumber(IValue::new_i64(i64::from(v)))
}
}
impl From<i16> for INumber {
fn from(v: i16) -> Self {
INumber(IValue::new_i64(i64::from(v)))
}
}
impl From<i8> for INumber {
fn from(v: i8) -> Self {
INumber(IValue::new_i64(i64::from(v)))
}
}
impl From<isize> for INumber {
fn from(v: isize) -> Self {
INumber(IValue::new_i64(v as i64))
}
}
impl TryFrom<f64> for INumber {
type Error = ();
fn try_from(v: f64) -> Result<Self, ()> {
IValue::new_f64(v).map(INumber).ok_or(())
}
}
impl TryFrom<f32> for INumber {
type Error = ();
fn try_from(v: f32) -> Result<Self, ()> {
IValue::new_f64(f64::from(v)).map(INumber).ok_or(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParseNumberError(());
impl fmt::Display for ParseNumberError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("invalid JSON number")
}
}
impl std::error::Error for ParseNumberError {}
impl FromStr for INumber {
type Err = ParseNumberError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use crate::value::inline::{
InlineNumber, InlineNumberError, InlineNumberRepr, NumberShape,
};
let float_from = |s: &str| {
s.parse::<f64>()
.ok()
.and_then(IValue::new_f64)
.ok_or(ParseNumberError(()))
};
#[cfg(feature = "arbitrary_precision")]
let spill = |s: &str, shape: &NumberShape| -> Result<IValue, ParseNumberError> {
use crate::value::inline::parse_json_number;
let parsed = parse_json_number(s).expect("`from_str` already validated the grammar");
match parsed.significand() {
Some((digits, exp)) => Ok(IValue::new_decimal(
parsed.negative,
&digits,
exp,
matches!(shape, NumberShape::Float),
)),
None => float_from(s),
}
};
#[cfg(not(feature = "arbitrary_precision"))]
let spill = |s: &str, _shape: &NumberShape| float_from(s);
let value = match InlineNumberRepr::from_str(s) {
Ok(bits) => unsafe { IValue::new_inline_number(bits) },
Err(InlineNumberError::Invalid) => return Err(ParseNumberError(())),
Err(InlineNumberError::Spill(shape @ NumberShape::Integer)) => {
if let Ok(v) = s.parse::<i64>() {
IValue::new_i64(v)
} else if let Ok(v) = s.parse::<u64>() {
IValue::new_u64(v)
} else {
spill(s, &shape)?
}
}
Err(InlineNumberError::Spill(shape @ NumberShape::Float)) => spill(s, &shape)?,
};
Ok(INumber(value))
}
}
impl From<serde_json::Number> for INumber {
fn from(n: serde_json::Number) -> Self {
if let Some(v) = n.as_u64() {
INumber::from(v)
} else if let Some(v) = n.as_i64() {
INumber::from(v)
} else {
n.to_string().parse::<INumber>().unwrap_or_else(|_| {
let v = n
.as_f64()
.expect("a serde_json number is always an integer or float");
INumber::try_from(v.clamp(f64::MIN, f64::MAX))
.expect("a clamped f64 is always finite")
})
}
}
}
impl From<INumber> for serde_json::Number {
fn from(n: INumber) -> Self {
if let Some(v) = n.to_u64() {
serde_json::Number::from(v)
} else if let Some(v) = n.to_i64() {
serde_json::Number::from(v)
} else {
serde_json::Number::from_f64(n.to_f64_lossy()).expect("an INumber is always finite")
}
}
}
impl PartialEq for INumber {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for INumber {}
impl Ord for INumber {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0).unwrap()
}
}
impl PartialOrd for INumber {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Debug for INumber {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Default for INumber {
fn default() -> Self {
Self::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn can_create() {
let x = INumber::zero();
let y: INumber = (0.0).try_into().unwrap();
assert_eq!(x, y);
assert!(!x.has_decimal_point());
assert!(y.has_decimal_point());
assert_eq!(x.to_i32(), Some(0));
assert_eq!(y.to_i32(), Some(0));
}
#[test]
fn stores_small_integers_inline() {
for v in [0i64, 1, -1, 42, -42, 1000, -1000, 1_000_000] {
let n = INumber::from(v);
assert!(n.0.is_inline(), "{} should be inline", v);
assert_eq!(n.to_i64(), Some(v));
assert!(!n.has_decimal_point());
}
}
#[test]
fn stores_short_decimals_inline() {
for (v, s) in [
(0.5f64, "0.5"),
(0.25, "0.25"),
(63.5, "63.5"),
(2.0, "2.0"),
] {
let n = INumber::try_from(v).unwrap();
assert!(n.0.is_inline(), "{} should be inline", s);
assert_eq!(n.to_f64(), Some(v), "{}", s);
assert!(n.has_decimal_point(), "{}", s);
}
}
#[test]
fn integer_and_float_compare_equal() {
let i = INumber::from(2);
let f = INumber::try_from(2.0).unwrap();
assert_eq!(i, f);
assert!(!i.has_decimal_point());
assert!(f.has_decimal_point());
}
#[mockalloc::test]
fn integer_boundaries_roundtrip() {
for v in [i64::MIN, i64::MIN + 1, -1, 0, 1, i64::MAX - 1, i64::MAX] {
assert_eq!(INumber::from(v).to_i64(), Some(v), "{}", v);
}
for v in [0u64, 1, i64::MAX as u64, i64::MAX as u64 + 1, u64::MAX] {
assert_eq!(INumber::from(v).to_u64(), Some(v), "{}", v);
}
let big_round = if usize::BITS == 64 {
10i64.pow(18)
} else {
10i64.pow(8)
};
for v in [big_round, -big_round] {
let n = INumber::from(v);
assert!(!n.0.is_inline(), "{} (integer) should be on the heap", v);
assert_eq!(n.to_i64(), Some(v));
assert!(!n.has_decimal_point());
let f = INumber::try_from(v as f64).unwrap();
#[cfg(feature = "arbitrary_precision")]
assert!(f.0.is_inline(), "{} (float) should factor inline", v);
assert_eq!(f.to_i64(), Some(v));
assert!(f.has_decimal_point());
}
for v in [
10i64.pow(15),
10i64.pow(18),
i64::MAX,
9_999_999_999_999_937,
] {
assert_eq!(INumber::from(v).to_i64(), Some(v), "{}", v);
}
}
#[test]
fn negative_short_decimals() {
for v in [-0.5f64, -2.5, -63.5, -0.125] {
let n = INumber::try_from(v).unwrap();
assert_eq!(n.to_f64(), Some(v));
assert!(n.has_decimal_point());
assert_eq!(-n.to_f64_lossy(), -v);
}
}
#[mockalloc::test]
fn large_values_use_heap() {
let big = INumber::from(u64::MAX);
assert!(!big.0.is_inline());
assert_eq!(big.to_u64(), Some(u64::MAX));
let pi = INumber::try_from(std::f64::consts::PI).unwrap();
assert!(!pi.0.is_inline());
assert_eq!(pi.to_f64(), Some(std::f64::consts::PI));
assert!(pi.has_decimal_point());
}
#[test]
fn ordering() {
let mut v = [
INumber::from(-5),
INumber::try_from(2.5).unwrap(),
INumber::from(2),
INumber::from(u64::MAX),
INumber::try_from(-0.5).unwrap(),
INumber::from(0),
];
v.sort();
let f: Vec<f64> = v.iter().map(INumber::to_f64_lossy).collect();
assert_eq!(f, [-5.0, -0.5, 0.0, 2.0, 2.5, u64::MAX as f64]);
}
#[mockalloc::test]
fn ordering_across_representations() {
let mut v = [
INumber::try_from(std::f64::consts::PI).unwrap(),
INumber::from(3),
INumber::from(u64::MAX),
INumber::from(i64::MIN),
INumber::try_from(2.999_999_999).unwrap(),
INumber::from(0),
];
v.sort();
let got: Vec<f64> = v.iter().map(INumber::to_f64_lossy).collect();
assert!(got.windows(2).all(|w| w[0] <= w[1]), "{:?}", got);
assert_eq!(got[0], i64::MIN as f64);
assert_eq!(*got.last().unwrap(), u64::MAX as f64);
}
#[test]
fn large_integer_and_enotation_float_serialize_distinctly() {
let int: IValue = serde_json::from_str("1000000000000000000").unwrap();
assert!(!int.as_number().unwrap().has_decimal_point());
assert_eq!(serde_json::to_string(&int).unwrap(), "1000000000000000000");
let float: IValue = serde_json::from_str("1e18").unwrap();
assert!(float.as_number().unwrap().has_decimal_point());
let s = serde_json::to_string(&float).unwrap();
assert!(
s.contains('.') || s.contains('e') || s.contains('E'),
"expected a float rendering, got {}",
s
);
assert_eq!(int, float);
}
#[test]
fn parses_valid_integers() {
for (s, v) in [
("0", 0i64),
("-0", 0),
("42", 42),
("-42", -42),
("9223372036854775807", i64::MAX),
("-9223372036854775808", i64::MIN),
] {
let n: INumber = s.parse().unwrap();
assert_eq!(n.to_i64(), Some(v), "{}", s);
assert!(!n.has_decimal_point(), "{} should have no decimal point", s);
}
let n: INumber = "18446744073709551615".parse().unwrap();
assert_eq!(n.to_u64(), Some(u64::MAX));
assert!(!n.has_decimal_point());
}
#[test]
fn parses_valid_floats() {
for (s, v) in [
("0.0", 0.0f64),
("-0.0", -0.0),
("1.5", 1.5),
("0.5", 0.5),
("-3.25", -3.25),
("1e3", 1000.0),
("1E3", 1000.0),
("1e+3", 1000.0),
("1e-3", 0.001),
("1.5e2", 150.0),
] {
let n: INumber = s.parse().unwrap();
assert_eq!(n.to_f64_lossy(), v, "{}", s);
assert!(n.has_decimal_point(), "{} should have a decimal point", s);
}
}
#[test]
#[cfg(feature = "arbitrary_precision")]
fn a_value_is_the_same_number_in_either_representation() {
fn hash_of(n: &INumber) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
n.hash(&mut h);
h.finish()
}
for (written, exactly_an_f64) in [
("18446744073709551616", 18_446_744_073_709_551_616.0_f64), ("100000000000000000000", 1e20),
("10000000000000000000000", 1e22),
(
"1267650600228229401496703205376",
1_267_650_600_228_229_401_496_703_205_376.0_f64,
),
] {
let as_integer: INumber = written.parse().unwrap();
let as_float = INumber::try_from(exactly_an_f64).unwrap();
assert_eq!(as_integer, as_float, "{} != its own f64", written);
assert_eq!(
hash_of(&as_integer),
hash_of(&as_float),
"{} and its f64 are equal but hash differently",
written
);
assert_eq!(as_integer.to_f64(), Some(exactly_an_f64), "{}", written);
assert!(!as_integer.has_decimal_point(), "{}", written);
assert!(as_float.has_decimal_point(), "{}", written);
assert_eq!(serde_json::to_string(&as_integer).unwrap(), written);
}
let big: INumber = "123456789012345678901234567890".parse().unwrap();
let same: INumber = "1.2345678901234567890123456789e29".parse().unwrap();
assert_eq!(big, same);
assert_eq!(hash_of(&big), hash_of(&same));
assert_eq!(big.to_f64(), None);
assert!(!big.has_decimal_point() && same.has_decimal_point());
}
#[test]
fn a_plain_integer_beyond_u64_stays_an_integer() {
let n: INumber = "100000000000000000000".parse().unwrap(); assert_eq!(n.to_f64_lossy(), 1e20);
#[cfg(not(feature = "arbitrary_precision"))]
{
assert!(n.has_decimal_point());
}
#[cfg(feature = "arbitrary_precision")]
{
assert!(!n.has_decimal_point());
assert_eq!(serde_json::to_string(&n).unwrap(), "100000000000000000000");
let odd: INumber = "123456789012345678901234567890".parse().unwrap();
assert!(!odd.has_decimal_point());
assert_eq!(odd.to_i64(), None);
assert_eq!(
serde_json::to_string(&odd).unwrap(),
"123456789012345678901234567890"
);
let as_float: INumber = "1.2345678901234567890123456789e29".parse().unwrap();
assert_eq!(odd, as_float);
assert!(as_float.has_decimal_point());
}
}
#[test]
fn rejects_invalid_json_numbers() {
for s in [
"",
" ",
"1 ",
" 1",
"+1",
"01",
"-01",
"00",
"1.",
".5",
"1.e2",
"1e",
"1e+",
"1e-",
"1..2",
"1.2.3",
"abc",
"NaN",
"Infinity",
"-Infinity",
"0x1f",
"1_000",
"1,000",
"--1",
"1-",
"e5",
".",
"-",
"+",
"0.",
"1.0.",
"0xa",
] {
assert!(s.parse::<INumber>().is_err(), "{:?} should be rejected", s);
}
}
#[test]
fn absurd_exponents_do_not_overflow() {
for s in [
"1e-99999999999999999999",
"1e99999999999999999999",
"-1.5e-99999999999999999999",
"0.00000000000000000001e-99999999999999999999",
"1e-9223372036854775808", ] {
let _ = s.parse::<INumber>();
}
}
#[test]
#[cfg(not(feature = "arbitrary_precision"))]
fn rejects_out_of_range_magnitude() {
assert!("1e400".parse::<INumber>().is_err());
assert!("-1e400".parse::<INumber>().is_err());
}
#[test]
#[cfg(feature = "arbitrary_precision")]
fn holds_magnitudes_beyond_f64() {
let big: INumber = "1e400".parse().unwrap();
assert!(big.has_decimal_point());
assert_eq!(big.to_f64(), None);
assert_eq!(big, "10e399".parse::<INumber>().unwrap());
assert!(big > "9.9e399".parse::<INumber>().unwrap());
assert!(big > INumber::try_from(f64::MAX).unwrap());
assert!("-1e400".parse::<INumber>().unwrap() < big);
assert_eq!(serde_json::to_string(&big).unwrap(), "1e400");
assert_eq!(
serde_json::to_string(&"1e-400".parse::<INumber>().unwrap()).unwrap(),
"1e-400"
);
}
#[test]
fn try_from_rejects_non_finite() {
for v in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(INumber::try_from(v).is_err(), "{} should be rejected", v);
}
for v in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert!(INumber::try_from(v).is_err(), "{} should be rejected", v);
}
assert!(INumber::try_from(1.5_f64).is_ok());
}
#[test]
fn round_trips_through_serialization() {
for s in ["0", "-42", "1.5", "1e3", "18446744073709551615"] {
let n: INumber = s.parse().unwrap();
let via_serde: INumber = serde_json::from_str(s).unwrap();
assert_eq!(n, via_serde, "{}", s);
assert_eq!(
n.has_decimal_point(),
via_serde.has_decimal_point(),
"{}",
s
);
}
for s in ["0", "-42", "1.5", "1e3", "0.001", "0.1", "3.5e-4"] {
let n: INumber = s.parse().unwrap();
let out = serde_json::to_string(&n).unwrap();
let back: INumber = out.parse().unwrap();
assert_eq!(n, back, "{} -> {}", s, out);
}
}
#[cfg(feature = "arbitrary_precision")]
#[test]
fn from_str_preserves_exact_decimals() {
let d: INumber = "0.1".parse().unwrap();
let f = INumber::try_from(0.1_f64).unwrap();
assert!(d.has_decimal_point());
assert_ne!(d, f, "exact 0.1 must differ from the f64 0.1");
assert!(d < f, "0.1 (exact) < 0.1_f64");
assert_eq!(d.to_f64(), None, "0.1 is not exactly an f64");
assert_eq!(d.to_f64_lossy(), 0.1_f64, "nearest f64");
assert_eq!(serde_json::to_string(&d).unwrap(), "0.1");
for s in ["0.10", "1e-1", "0.100000"] {
assert_eq!(s.parse::<INumber>().unwrap(), d, "{}", s);
}
let half: INumber = "0.5".parse().unwrap();
assert_eq!(half, INumber::try_from(0.5_f64).unwrap());
assert_eq!(half.to_f64(), Some(0.5));
let pi: INumber = "3.141592653589793".parse().unwrap();
assert_ne!(pi, INumber::try_from(std::f64::consts::PI).unwrap());
assert_eq!(pi.to_f64(), None);
assert_eq!(pi.to_f64_lossy(), std::f64::consts::PI);
assert_eq!(serde_json::to_string(&pi).unwrap(), "3.141592653589793");
}
}