use serde_json::Value;
use std::cmp::Ordering;
mod f32;
mod float;
mod int;
pub use f32::ZF32;
pub use float::ZNumber;
pub use int::{IntType, ZInt, ZInteger};
use crate::error::{IssueCode, SizeOrigin};
pub(super) const MAX_SAFE: i128 = 9_007_199_254_740_991;
#[derive(Clone, Copy, Debug)]
pub(super) enum Num {
F(f64),
I(i128),
}
impl Num {
pub(super) fn as_f64(self) -> f64 {
match self {
Num::F(f) => f,
Num::I(i) => i as f64,
}
}
pub(super) fn cmp(self, other: Num) -> Option<Ordering> {
match (self, other) {
(Num::I(a), Num::I(b)) => Some(a.cmp(&b)),
(a, b) => a.as_f64().partial_cmp(&b.as_f64()),
}
}
pub(super) fn json(self) -> Value {
match self {
Num::F(f) => serde_json::json!(f),
Num::I(i) => match (i64::try_from(i), u64::try_from(i)) {
(Ok(v), _) => v.into(),
(_, Ok(v)) => v.into(),
_ => serde_json::json!(i as f64),
},
}
}
}
impl std::fmt::Display for Num {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Num::F(v) => write!(f, "{}", v),
Num::I(v) => write!(f, "{}", v),
}
}
}
#[derive(Clone, Copy)]
pub(super) enum Kind {
Min(Num),
Max(Num),
Gt(Num),
Lt(Num),
Positive,
Negative,
NonNegative,
NonPositive,
MultipleOf(Num),
Safe,
}
#[derive(Clone)]
pub(super) struct NumberCheck {
pub(super) kind: Kind,
pub(super) msg: Option<String>,
}
impl Kind {
pub(super) fn key(self) -> &'static str {
match self {
Kind::Min(_) | Kind::Gt(_) => "too_small",
Kind::Max(_) | Kind::Lt(_) => "too_big",
Kind::Positive => "not_positive",
Kind::Negative => "not_negative",
Kind::NonNegative => "not_non_negative",
Kind::NonPositive => "not_non_positive",
Kind::MultipleOf(_) => "not_multiple_of",
Kind::Safe => "not_safe",
}
}
pub(super) fn lower(self) -> Option<(Num, bool)> {
match self {
Kind::Min(v) => Some((v, false)),
Kind::Gt(v) => Some((v, true)),
Kind::Positive => Some((Num::I(0), true)),
Kind::NonNegative => Some((Num::I(0), false)),
Kind::Safe => Some((Num::I(-MAX_SAFE), false)),
_ => None,
}
}
pub(super) fn upper(self) -> Option<(Num, bool)> {
match self {
Kind::Max(v) => Some((v, false)),
Kind::Lt(v) => Some((v, true)),
Kind::Negative => Some((Num::I(0), true)),
Kind::NonPositive => Some((Num::I(0), false)),
Kind::Safe => Some((Num::I(MAX_SAFE), false)),
_ => None,
}
}
pub(super) fn failure(self, n: Num) -> Option<IssueCode> {
match self {
Kind::MultipleOf(v) => {
if is_multiple_of(n, v) {
None
} else {
Some(IssueCode::NotMultipleOf { divisor: v.json() })
}
}
Kind::Safe => {
let ok = match n {
Num::I(i) => (-MAX_SAFE..=MAX_SAFE).contains(&i),
Num::F(f) => (-(MAX_SAFE as f64)..=MAX_SAFE as f64).contains(&f),
};
if ok {
None
} else {
Some(IssueCode::NotSafeInteger)
}
}
_ => {
if let Some((b, excl)) = self.lower() {
let ok = matches!(
(n.cmp(b), excl),
(Some(Ordering::Greater), _) | (Some(Ordering::Equal), false)
);
if !ok {
return Some(IssueCode::TooSmall {
origin: SizeOrigin::Number,
minimum: b.json(),
inclusive: !excl,
});
}
}
if let Some((b, excl)) = self.upper() {
let ok = matches!(
(n.cmp(b), excl),
(Some(Ordering::Less), _) | (Some(Ordering::Equal), false)
);
if !ok {
return Some(IssueCode::TooBig {
origin: SizeOrigin::Number,
maximum: b.json(),
inclusive: !excl,
});
}
}
None
}
}
}
}
fn is_multiple_of(n: Num, v: Num) -> bool {
match (n, v) {
(_, Num::I(0)) => false,
(Num::I(a), Num::I(b)) => a.checked_rem(b).is_none_or(|r| r == 0),
(n, v) => {
let (n, v) = (n.as_f64(), v.as_f64());
if v == 0.0 || !n.is_finite() || !v.is_finite() {
return false;
}
let q = n / v;
(q - q.round()).abs() <= 1e-9 * q.abs().max(1.0)
}
}
}