use std::{cmp::Ordering, fmt::Display};
use serde::{Deserialize, Serialize};
use crate::{Interpolatable, Size};
#[derive(Clone, Serialize, Deserialize)]
#[serde(crate = "crate::serde")]
#[derive(Debug, Copy)]
pub enum Numeric {
I8(i8),
I16(i16),
I32(i32),
I64(i64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
F64(f64),
F32(f32),
ISize(isize),
USize(usize),
}
impl Display for Numeric {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt_num<V: Display>(
f: &mut std::fmt::Formatter<'_>,
v: V,
) -> Result<(), std::fmt::Error> {
write!(f, "{:.2}", v)
}
match self {
Numeric::I8(v) => fmt_num(f, v),
Numeric::I16(v) => fmt_num(f, v),
Numeric::I32(v) => fmt_num(f, v),
Numeric::I64(v) => fmt_num(f, v),
Numeric::U8(v) => fmt_num(f, v),
Numeric::U16(v) => fmt_num(f, v),
Numeric::U32(v) => fmt_num(f, v),
Numeric::U64(v) => fmt_num(f, v),
Numeric::F64(v) => fmt_num(f, v),
Numeric::F32(v) => fmt_num(f, v),
Numeric::ISize(v) => fmt_num(f, v),
Numeric::USize(v) => fmt_num(f, v),
}
}
}
impl PartialEq for Numeric {
fn eq(&self, rhs: &Self) -> bool {
match (self.is_float(), rhs.is_float()) {
(false, false) => self.to_int() == rhs.to_int(),
_ => (self.to_float() - rhs.to_float()) < 1e-6,
}
}
}
impl Default for Numeric {
fn default() -> Self {
Self::F64(0.0)
}
}
macro_rules! impl_numeric_arith {
($trait:ident, $method:ident, $op:tt) => {
impl std::ops::$trait for &Numeric {
type Output = Numeric;
fn $method(self, rhs: Self) -> Self::Output {
match (self.is_float(), rhs.is_float()) {
(false, false) => Numeric::I64(self.to_int() $op rhs.to_int()),
_ => Numeric::F64(self.to_float() $op rhs.to_float()),
}
}
}
impl std::ops::$trait for Numeric {
type Output = Numeric;
fn $method(self, rhs: Self) -> Self::Output {
&self $op &rhs
}
}
};
}
impl_numeric_arith!(Add, add, +);
impl_numeric_arith!(Sub, sub, -);
impl_numeric_arith!(Mul, mul, *);
impl_numeric_arith!(Div, div, /);
impl_numeric_arith!(Rem, rem, %);
impl std::ops::Neg for Numeric {
type Output = Self;
fn neg(self) -> Self::Output {
use Numeric::*;
match self {
I8(a) => I8(-a),
I16(a) => I16(-a),
I32(a) => I32(-a),
I64(a) => I64(-a),
F32(a) => F32(-a),
F64(a) => F64(-a),
ISize(a) => ISize(-a),
_ => panic!("tried to negate numeric that is unsigned"),
}
}
}
impl PartialOrd for Numeric {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
match (self.is_float(), rhs.is_float()) {
(false, false) => self.to_int().partial_cmp(&rhs.to_int()),
_ => self.to_float().partial_cmp(&rhs.to_float()),
}
}
}
macro_rules! impl_to_from {
($return_type:ty, $Variant:path) => {
impl From<&Numeric> for $return_type {
fn from(value: &Numeric) -> Self {
use Numeric::*;
match *value {
I8(a) => a as $return_type,
I16(a) => a as $return_type,
I32(a) => a as $return_type,
I64(a) => a as $return_type,
U8(a) => a as $return_type,
U16(a) => a as $return_type,
U32(a) => a as $return_type,
U64(a) => a as $return_type,
F32(a) => a as $return_type,
F64(a) => a as $return_type,
ISize(a) => a as $return_type,
USize(a) => a as $return_type,
}
}
}
impl From<Numeric> for $return_type {
fn from(value: Numeric) -> Self {
(&value).into()
}
}
impl From<$return_type> for Numeric {
fn from(value: $return_type) -> Self {
$Variant(value)
}
}
impl From<&$return_type> for Numeric {
fn from(value: &$return_type) -> Self {
$Variant(*value)
}
}
};
}
impl_to_from!(f32, Numeric::F32);
impl_to_from!(f64, Numeric::F64);
impl_to_from!(i8, Numeric::I8);
impl_to_from!(i16, Numeric::I16);
impl_to_from!(i32, Numeric::I32);
impl_to_from!(i64, Numeric::I64);
impl_to_from!(u8, Numeric::U8);
impl_to_from!(u16, Numeric::U16);
impl_to_from!(u32, Numeric::U32);
impl_to_from!(u64, Numeric::U64);
impl_to_from!(isize, Numeric::ISize);
impl_to_from!(usize, Numeric::USize);
impl Numeric {
pub fn to_float(&self) -> f64 {
self.into()
}
pub fn to_int(&self) -> i64 {
self.into()
}
pub fn is_float(&self) -> bool {
match self {
Numeric::F64(_) | Numeric::F32(_) => true,
_ => false,
}
}
pub fn pow(self, exp: Self) -> Self {
match (self.is_float(), exp.is_float()) {
(false, false) => Numeric::I64(self.to_int().pow(exp.into())),
_ => Numeric::F64(self.to_float().powf(exp.to_float())),
}
}
pub fn min(self, other: Self) -> Self {
match (self.is_float(), other.is_float()) {
(false, false) => Numeric::I64(self.to_int().min(other.to_int())),
_ => Numeric::F64(self.to_float().min(other.to_float())),
}
}
pub fn max(self, other: Self) -> Self {
match (self.is_float(), other.is_float()) {
(false, false) => Numeric::I64(self.to_int().max(other.to_int())),
_ => Numeric::F64(self.to_float().max(other.to_float())),
}
}
}
impl Interpolatable for Numeric {
fn interpolate(&self, other: &Self, t: f64) -> Self {
Numeric::F64(Into::<f64>::into(self).interpolate(&other.into(), t))
}
}
impl From<Size> for Numeric {
fn from(value: Size) -> Self {
match value {
Size::Pixels(x) | Size::Percent(x) => x,
Size::Combined(_, _) => unreachable!("Cannot coerce a combined size to a numeric"),
}
}
}