use std::{cmp::Ordering, fmt::Display};
use serde::Serialize;
use crate::errors::JsonError;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Sign {
Negative,
Positive,
}
impl Display for Sign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Sign::Negative => write!(f, "-"),
Sign::Positive => write!(f, "+"),
}
}
}
#[derive(Debug, Clone, PartialOrd)]
pub enum Number {
UnsignedInteger(u64),
UnsignedBigInteger(u128),
SignedInteger(i64),
SignedBigInteger(i128),
Float(f64),
}
impl TryFrom<serde_json::Number> for Number {
type Error = JsonError;
fn try_from(value: serde_json::Number) -> Result<Self, Self::Error> {
if value.is_i64() {
Ok(Self::SignedInteger(value.as_i64().ok_or(
JsonError::Number("hint was i64, but i64 was not returned".to_string()),
)?))
} else if value.is_u64() {
Ok(Self::UnsignedInteger(value.as_u64().ok_or(
JsonError::Number("hint was u64, but u64 was not returned".to_string()),
)?))
} else if value.is_f64() {
Ok(Self::Float(value.as_f64().ok_or(JsonError::Number(
"hint was f64, but f64 was not returned".to_string(),
))?))
} else {
Err(JsonError::Number(format!(
"unrecognized number type: {}",
value
)))
}
}
}
impl Display for Number {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Number::UnsignedInteger(value) => write!(f, "{}", value),
Number::UnsignedBigInteger(value) => write!(f, "{}", value),
Number::SignedInteger(value) => write!(f, "{}", value),
Number::SignedBigInteger(value) => write!(f, "{}", value),
Number::Float(value) => write!(f, "{}", value),
}
}
}
impl PartialEq for Number {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::UnsignedInteger(a), Self::UnsignedInteger(b)) => a == b,
(Self::UnsignedBigInteger(a), Self::UnsignedBigInteger(b)) => a == b,
(Self::SignedInteger(a), Self::SignedInteger(b)) => a == b,
(Self::SignedBigInteger(a), Self::SignedBigInteger(b)) => a == b,
(Self::Float(a), Self::Float(b)) => a == b,
_ => {
self.sign() == other.sign()
&& self.integer_part() == other.integer_part()
&& self.fractional_part() == other.fractional_part()
}
}
}
}
impl Eq for Number {}
impl Ord for Number {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Number::UnsignedInteger(a), Number::UnsignedInteger(b)) => a.cmp(b),
(Number::UnsignedBigInteger(a), Number::UnsignedBigInteger(b)) => a.cmp(b),
(Number::SignedInteger(a), Number::SignedInteger(b)) => a.cmp(b),
(Number::SignedBigInteger(a), Number::SignedBigInteger(b)) => a.cmp(b),
(Number::Float(a), Number::Float(b)) => a.total_cmp(b),
_ => {
if self == other {
Ordering::Equal
} else {
match self.sign().cmp(&other.sign()) {
Ordering::Less => Ordering::Less,
Ordering::Equal => match self.integer_part().cmp(&other.integer_part()) {
Ordering::Less => Ordering::Less,
Ordering::Equal => {
if self.fractional_part() < other.fractional_part() {
Ordering::Less
} else {
Ordering::Greater
}
}
Ordering::Greater => Ordering::Greater,
},
Ordering::Greater => Ordering::Greater,
}
}
}
}
}
}
impl From<i8> for Number {
fn from(value: i8) -> Self {
Self::SignedInteger(value.into())
}
}
impl From<i16> for Number {
fn from(value: i16) -> Self {
Self::SignedInteger(value.into())
}
}
impl From<i32> for Number {
fn from(value: i32) -> Self {
Self::SignedInteger(value.into())
}
}
impl From<i64> for Number {
fn from(value: i64) -> Self {
Self::SignedInteger(value)
}
}
impl From<i128> for Number {
fn from(value: i128) -> Self {
Self::SignedBigInteger(value)
}
}
impl From<u8> for Number {
fn from(value: u8) -> Self {
Self::UnsignedInteger(value.into())
}
}
impl From<u16> for Number {
fn from(value: u16) -> Self {
Self::UnsignedInteger(value.into())
}
}
impl From<u32> for Number {
fn from(value: u32) -> Self {
Self::UnsignedInteger(value.into())
}
}
impl From<u64> for Number {
fn from(value: u64) -> Self {
Self::UnsignedInteger(value.into())
}
}
impl From<u128> for Number {
fn from(value: u128) -> Self {
Self::UnsignedBigInteger(value)
}
}
impl From<f32> for Number {
fn from(value: f32) -> Self {
Self::Float(value.into())
}
}
impl From<f64> for Number {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Number::UnsignedInteger(value) => serializer.serialize_u64(*value),
Number::UnsignedBigInteger(value) => serializer.serialize_u128(*value),
Number::SignedInteger(value) => serializer.serialize_i64(*value),
Number::SignedBigInteger(value) => serializer.serialize_i128(*value),
Number::Float(value) => serializer.serialize_f64(*value),
}
}
}
impl Number {
pub fn sign(&self) -> Sign {
match self {
Number::UnsignedInteger(_) => Sign::Positive,
Number::UnsignedBigInteger(_) => Sign::Positive,
Number::SignedInteger(value) => {
if *value < 0 {
Sign::Negative
} else {
Sign::Positive
}
}
Number::SignedBigInteger(value) => {
if *value < 0 {
Sign::Negative
} else {
Sign::Positive
}
}
Number::Float(value) => {
if *value < 0.0 {
Sign::Negative
} else {
Sign::Positive
}
}
}
}
pub fn integer_part(&self) -> u128 {
match self {
Number::UnsignedInteger(value) => (*value).into(),
Number::UnsignedBigInteger(value) => *value,
Number::SignedInteger(value) => value.unsigned_abs().into(),
Number::SignedBigInteger(value) => value.unsigned_abs(),
Number::Float(value) => value.abs() as u128,
}
}
pub fn fractional_part(&self) -> Option<f64> {
match self {
Number::Float(value) => {
if value.abs().fract() == 0.0 {
None
} else {
Some(value.abs().fract())
}
}
_ => None,
}
}
}
#[cfg(test)]
mod test {
use std::cmp::Ordering;
use crate::number::Number;
const NUMBER_ORDERING_TEST_CASES: &[(Number, Number, Ordering)] = &[
(
Number::SignedInteger(0),
Number::SignedInteger(0),
Ordering::Equal,
),
(
Number::SignedInteger(0),
Number::SignedInteger(1),
Ordering::Less,
),
(
Number::SignedInteger(1),
Number::SignedInteger(-1),
Ordering::Greater,
),
(
Number::UnsignedInteger(1),
Number::SignedInteger(1),
Ordering::Equal,
),
(
Number::SignedInteger(-1),
Number::UnsignedInteger(1),
Ordering::Less,
),
(
Number::Float(1.5),
Number::UnsignedBigInteger(2),
Ordering::Less,
),
(
Number::SignedInteger(2),
Number::Float(1.5),
Ordering::Greater,
),
];
#[test]
fn number_ordering_works() {
for test_case in NUMBER_ORDERING_TEST_CASES {
let (a, b, expected_ordering) = test_case;
assert_eq!(
a.cmp(b),
*expected_ordering,
"testing {:?} vs {:?}, expecting {:?}",
a,
b,
*expected_ordering,
);
}
}
}