use crate::KValue;
use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
ops,
};
#[allow(missing_docs)]
#[derive(Clone, Copy)]
pub enum KNumber {
F64(f64),
I64(i64),
}
impl KNumber {
#[must_use]
pub fn abs(self) -> Self {
match self {
Self::F64(n) => Self::F64(n.abs()),
Self::I64(n) => Self::I64(n.abs()),
}
}
#[must_use]
pub fn ceil(self) -> Self {
match self {
Self::F64(n) => Self::I64(n.ceil() as i64),
Self::I64(n) => Self::I64(n),
}
}
#[must_use]
pub fn floor(self) -> Self {
match self {
Self::F64(n) => Self::I64(n.floor() as i64),
Self::I64(n) => Self::I64(n),
}
}
#[must_use]
pub fn round(self) -> Self {
match self {
Self::F64(n) => Self::I64(n.round() as i64),
Self::I64(n) => Self::I64(n),
}
}
pub fn is_f64(self) -> bool {
matches!(self, Self::F64(_))
}
pub fn is_i64(self) -> bool {
matches!(self, Self::I64(_))
}
pub fn is_i64_in_f64_range(&self) -> bool {
if let Self::I64(n) = *self {
(n as f64 as i64) == n
} else {
false
}
}
pub fn is_finite(self) -> bool {
match self {
Self::F64(n) => n.is_finite(),
Self::I64(_) => true,
}
}
pub fn is_nan(self) -> bool {
match self {
Self::F64(n) => n.is_nan(),
Self::I64(_) => false,
}
}
#[must_use]
pub fn pow(self, other: Self) -> Self {
use KNumber::{F64, I64};
match (self, other) {
(F64(a), F64(b)) => F64(a.powf(b)),
(F64(a), I64(b)) => F64(a.powf(b as f64)),
(I64(a), F64(b)) => F64((a as f64).powf(b)),
(I64(a), I64(b)) => {
if b < 0 {
F64((a as f64).powf(b as f64))
} else {
I64(a.wrapping_pow(b as u32))
}
}
}
}
pub fn to_bits(self) -> u64 {
match self {
Self::F64(n) => n.to_bits(),
Self::I64(n) => n as u64,
}
}
}
impl fmt::Debug for KNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KNumber::F64(n) => write!(f, "Float({n})"),
KNumber::I64(n) => write!(f, "Int({n})"),
}
}
}
impl fmt::Display for KNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KNumber::F64(n) => {
if n.fract() == 0.0 {
write!(f, "{n:.1}")
} else {
write!(f, "{n}")
}
}
KNumber::I64(n) => write!(f, "{n}"),
}
}
}
impl Hash for KNumber {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.to_bits())
}
}
impl PartialEq for KNumber {
fn eq(&self, other: &Self) -> bool {
use KNumber::*;
match (self, other) {
(F64(a), F64(b)) => a == b,
(F64(a), I64(b)) => *a == *b as f64,
(I64(a), F64(b)) => *a as f64 == *b,
(I64(a), I64(b)) => a == b,
}
}
}
impl Eq for KNumber {}
impl PartialOrd for KNumber {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for KNumber {
fn cmp(&self, other: &Self) -> Ordering {
use KNumber::*;
let result = match (self, other) {
(F64(a), F64(b)) => a.partial_cmp(b),
(F64(a), I64(b)) => a.partial_cmp(&(*b as f64)),
(I64(a), F64(b)) => (*a as f64).partial_cmp(b),
(I64(a), I64(b)) => a.partial_cmp(b),
};
match result {
Some(result) => result,
None => match (self.is_nan(), other.is_nan()) {
(false, true) => Ordering::Less,
(true, false) => Ordering::Greater,
_ => Ordering::Equal,
},
}
}
}
impl ops::Neg for KNumber {
type Output = KNumber;
fn neg(self) -> KNumber {
use KNumber::*;
match self {
F64(n) => F64(-n),
I64(n) => I64(n.wrapping_neg()),
}
}
}
impl ops::Neg for &KNumber {
type Output = KNumber;
fn neg(self) -> KNumber {
use KNumber::*;
match *self {
F64(n) => F64(-n),
I64(n) => I64(n.wrapping_neg()),
}
}
}
macro_rules! impl_from_knumber_ref {
($($type:ty),+) => {
$(
impl From<&KNumber> for $type {
fn from(n: &KNumber) -> $type {
<$type>::from(*n)
}
}
)+
};
}
macro_rules! impl_from_number_extras {
($($type:ty),+) => {
$(
impl From<&$type> for KNumber {
fn from(n: &$type) -> KNumber {
Self::from(*n)
}
}
impl From<$type> for KValue {
fn from(value: $type) -> Self {
Self::Number(value.into())
}
}
impl From<&$type> for KValue {
fn from(value: &$type) -> Self {
Self::from(*value)
}
}
)+
};
}
macro_rules! number_traits_float {
($($type:ty),+) => {
$(
impl From<$type> for KNumber {
fn from(n: $type) -> KNumber {
KNumber::F64(n as f64)
}
}
impl_from_knumber_ref!($type);
impl From<KNumber> for $type {
fn from(n: KNumber) -> $type {
match n {
KNumber::F64(f) => f as $type,
KNumber::I64(i) => i as $type,
}
}
}
impl_from_number_extras!($type);
impl PartialEq<$type> for KNumber {
fn eq(&self, b: &$type) -> bool {
let b = *b as f64;
match self {
KNumber::F64(a) => *a == b,
KNumber::I64(a) => *a as f64 == b,
}
}
}
impl PartialOrd<$type> for KNumber {
fn partial_cmp(&self, b: &$type) -> Option<Ordering> {
let b = *b as f64;
match self {
KNumber::F64(a) => a.partial_cmp(&b),
KNumber::I64(a) => (*a as f64).partial_cmp(&b),
}
}
}
)+
};
}
macro_rules! number_traits_int {
($($type:ty),+) => {
$(
impl From<$type> for KNumber {
fn from(n: $type) -> KNumber {
use saturating_cast::SaturatingCast;
KNumber::I64(n.saturating_cast())
}
}
impl_from_knumber_ref!($type);
impl From<KNumber> for $type {
fn from(n: KNumber) -> $type {
use saturating_cast::SaturatingCast;
match n {
KNumber::F64(f) => f as $type,
KNumber::I64(i) => i.saturating_cast(),
}
}
}
impl_from_number_extras!($type);
impl PartialEq<$type> for KNumber {
fn eq(&self, b: &$type) -> bool {
let b = *b as i64;
match self {
KNumber::F64(a) => (*a as i64) == b,
KNumber::I64(a) => *a == b,
}
}
}
impl PartialOrd<$type> for KNumber {
fn partial_cmp(&self, b: &$type) -> Option<Ordering> {
let b = *b as i64;
match self {
KNumber::F64(a) => (*a as i64).partial_cmp(&b),
KNumber::I64(a) => a.partial_cmp(&b),
}
}
}
)+
};
}
number_traits_float!(f32, f64);
number_traits_int!(
i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize
);
macro_rules! number_op {
($trait:ident, $fn:ident, $f64_op:tt, $i64_op:ident) => {
impl ops::$trait for KNumber {
type Output = KNumber;
fn $fn(self, other: KNumber) -> KNumber {
use KNumber::*;
match (self, other) {
(F64(a), F64(b)) => F64(a $f64_op b),
(F64(a), I64(b)) => F64(a $f64_op b as f64),
(I64(a), F64(b)) => F64(a as f64 $f64_op b),
(I64(a), I64(b)) => I64(a.$i64_op(b)),
}
}
}
impl ops::$trait for &KNumber {
type Output = KNumber;
fn $fn(self, other: &KNumber) -> KNumber {
use KNumber::*;
match (*self, *other) {
(F64(a), F64(b)) => F64(a $f64_op b),
(F64(a), I64(b)) => F64(a $f64_op b as f64),
(I64(a), F64(b)) => F64(a as f64 $f64_op b),
(I64(a), I64(b)) => I64(a.$i64_op(b)),
}
}
}
};
}
number_op!(Add, add, +, wrapping_add);
number_op!(Sub, sub, -, wrapping_sub);
number_op!(Mul, mul, *, wrapping_mul);
number_op!(Rem, rem, %, wrapping_rem);
impl ops::Div for KNumber {
type Output = KNumber;
fn div(self, other: KNumber) -> KNumber {
use KNumber::*;
match (self, other) {
(F64(a), F64(b)) => F64(a / b),
(F64(a), I64(b)) => F64(a / b as f64),
(I64(a), F64(b)) => F64(a as f64 / b),
(I64(a), I64(b)) => F64(a as f64 / b as f64),
}
}
}
impl ops::Div for &KNumber {
type Output = KNumber;
fn div(self, other: &KNumber) -> KNumber {
use KNumber::*;
match (*self, *other) {
(F64(a), F64(b)) => F64(a / b),
(F64(a), I64(b)) => F64(a / b as f64),
(I64(a), F64(b)) => F64(a as f64 / b),
(I64(a), I64(b)) => F64(a as f64 / b as f64),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_out_of_range_saturates() {
assert_eq!(u8::from(KNumber::from(1000)), u8::MAX);
assert_eq!(i8::from(KNumber::from(-1000)), i8::MIN);
}
}