Skip to main content

pax_runtime_api/pax_value/
numeric.rs

1use std::{cmp::Ordering, fmt::Display};
2
3use crate::{Interpolatable, Size};
4use serde::{Deserialize, Serialize};
5use std::hash::Hash;
6
7/// Numeric type wrapper for PAXEL and runtime polymorphic numeric operations.
8/// This broad polyfill specifically enables terse numeric operations within expressions, without
9/// the overhead of explicit typing or numeric microsyntax.
10#[derive(Clone, Serialize, Deserialize)]
11#[serde(crate = "crate::serde")]
12#[derive(Debug, Copy)]
13pub enum Numeric {
14    I8(i8),
15    I16(i16),
16    I32(i32),
17    I64(i64),
18    U8(u8),
19    U16(u16),
20    U32(u32),
21    U64(u64),
22    F64(f64),
23    F32(f32),
24    ISize(isize),
25    USize(usize),
26}
27
28impl Hash for Numeric {
29    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30        match self {
31            Numeric::I8(v) => v.hash(state),
32            Numeric::I16(v) => v.hash(state),
33            Numeric::I32(v) => v.hash(state),
34            Numeric::I64(v) => v.hash(state),
35            Numeric::U8(v) => v.hash(state),
36            Numeric::U16(v) => v.hash(state),
37            Numeric::U32(v) => v.hash(state),
38            Numeric::U64(v) => v.hash(state),
39            Numeric::F64(v) => v.to_bits().hash(state),
40            Numeric::F32(v) => v.to_bits().hash(state),
41            Numeric::ISize(v) => v.hash(state),
42            Numeric::USize(v) => v.hash(state),
43        }
44    }
45}
46
47impl Display for Numeric {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        fn fmt_num<V: Display>(
50            f: &mut std::fmt::Formatter<'_>,
51            v: V,
52        ) -> Result<(), std::fmt::Error> {
53            write!(f, "{:.2}", v)
54        }
55        match self {
56            Numeric::I8(v) => fmt_num(f, v),
57            Numeric::I16(v) => fmt_num(f, v),
58            Numeric::I32(v) => fmt_num(f, v),
59            Numeric::I64(v) => fmt_num(f, v),
60            Numeric::U8(v) => fmt_num(f, v),
61            Numeric::U16(v) => fmt_num(f, v),
62            Numeric::U32(v) => fmt_num(f, v),
63            Numeric::U64(v) => fmt_num(f, v),
64            Numeric::F64(v) => fmt_num(f, v),
65            Numeric::F32(v) => fmt_num(f, v),
66            Numeric::ISize(v) => fmt_num(f, v),
67            Numeric::USize(v) => fmt_num(f, v),
68        }
69    }
70}
71
72impl PartialEq for Numeric {
73    fn eq(&self, rhs: &Self) -> bool {
74        match (self.is_float(), rhs.is_float()) {
75            (false, false) => self.to_int() == rhs.to_int(),
76            _ => (self.to_float() - rhs.to_float()).abs() < 1e-6,
77        }
78    }
79}
80
81impl Default for Numeric {
82    fn default() -> Self {
83        Self::F64(0.0)
84    }
85}
86
87macro_rules! impl_numeric_arith {
88    ($trait:ident, $method:ident, $op:tt) => {
89        impl std::ops::$trait for &Numeric {
90            type Output = Numeric;
91
92            fn $method(self, rhs: Self) -> Self::Output {
93
94                // TBD: might want to be more granular here at some point
95                match (self.is_float(), rhs.is_float()) {
96                    (false, false) => Numeric::I64(self.to_int() $op rhs.to_int()),
97                    _ => Numeric::F64(self.to_float() $op rhs.to_float()),
98                }
99            }
100        }
101        impl std::ops::$trait for Numeric {
102            type Output = Numeric;
103
104            fn $method(self, rhs: Self) -> Self::Output {
105                &self $op &rhs
106            }
107        }
108    };
109}
110
111impl_numeric_arith!(Add, add, +);
112impl_numeric_arith!(Sub, sub, -);
113impl_numeric_arith!(Mul, mul, *);
114impl_numeric_arith!(Div, div, /);
115impl_numeric_arith!(Rem, rem, %);
116
117impl std::ops::Neg for Numeric {
118    type Output = Self;
119
120    fn neg(self) -> Self::Output {
121        use Numeric::*;
122        match self {
123            I8(a) => I8(-a),
124            I16(a) => I16(-a),
125            I32(a) => I32(-a),
126            I64(a) => I64(-a),
127            F32(a) => F32(-a),
128            F64(a) => F64(-a),
129            ISize(a) => ISize(-a),
130            _ => panic!("tried to negate numeric that is unsigned"),
131        }
132    }
133}
134
135impl PartialOrd for Numeric {
136    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
137        match (self.is_float(), rhs.is_float()) {
138            (false, false) => self.to_int().partial_cmp(&rhs.to_int()),
139            _ => self.to_float().partial_cmp(&rhs.to_float()),
140        }
141    }
142}
143
144macro_rules! impl_to_from {
145    ($return_type:ty, $Variant:path) => {
146        impl From<&Numeric> for $return_type {
147            fn from(value: &Numeric) -> Self {
148                use Numeric::*;
149                match *value {
150                    I8(a) => a as $return_type,
151                    I16(a) => a as $return_type,
152                    I32(a) => a as $return_type,
153                    I64(a) => a as $return_type,
154                    U8(a) => a as $return_type,
155                    U16(a) => a as $return_type,
156                    U32(a) => a as $return_type,
157                    U64(a) => a as $return_type,
158                    F32(a) => a as $return_type,
159                    F64(a) => a as $return_type,
160                    ISize(a) => a as $return_type,
161                    USize(a) => a as $return_type,
162                }
163            }
164        }
165
166        impl From<Numeric> for $return_type {
167            fn from(value: Numeric) -> Self {
168                (&value).into()
169            }
170        }
171
172        impl From<$return_type> for Numeric {
173            fn from(value: $return_type) -> Self {
174                $Variant(value)
175            }
176        }
177        impl From<&$return_type> for Numeric {
178            fn from(value: &$return_type) -> Self {
179                $Variant(*value)
180            }
181        }
182    };
183}
184
185impl_to_from!(f32, Numeric::F32);
186impl_to_from!(f64, Numeric::F64);
187impl_to_from!(i8, Numeric::I8);
188impl_to_from!(i16, Numeric::I16);
189impl_to_from!(i32, Numeric::I32);
190impl_to_from!(i64, Numeric::I64);
191impl_to_from!(u8, Numeric::U8);
192impl_to_from!(u16, Numeric::U16);
193impl_to_from!(u32, Numeric::U32);
194impl_to_from!(u64, Numeric::U64);
195impl_to_from!(isize, Numeric::ISize);
196impl_to_from!(usize, Numeric::USize);
197
198impl Numeric {
199    /// Coerces this numeric value to `f64`.
200    pub fn to_float(&self) -> f64 {
201        self.into()
202    }
203
204    /// Coerces this numeric value to `i64`.
205    pub fn to_int(&self) -> i64 {
206        self.into()
207    }
208
209    /// Returns true when this value is stored as a floating-point number.
210    pub fn is_float(&self) -> bool {
211        match self {
212            Numeric::F64(_) | Numeric::F32(_) => true,
213            _ => false,
214        }
215    }
216
217    /// Raises this number to `exp`, preserving integer arithmetic when both sides are integral.
218    pub fn pow(self, exp: Self) -> Self {
219        match (self.is_float(), exp.is_float()) {
220            (false, false) => Numeric::I64(self.to_int().pow(exp.into())),
221            _ => Numeric::F64(self.to_float().powf(exp.to_float())),
222        }
223    }
224
225    /// Returns the smaller of two numeric values.
226    pub fn min(self, other: Self) -> Self {
227        match (self.is_float(), other.is_float()) {
228            (false, false) => Numeric::I64(self.to_int().min(other.to_int())),
229            _ => Numeric::F64(self.to_float().min(other.to_float())),
230        }
231    }
232
233    /// Returns the larger of two numeric values.
234    pub fn max(self, other: Self) -> Self {
235        match (self.is_float(), other.is_float()) {
236            (false, false) => Numeric::I64(self.to_int().max(other.to_int())),
237            _ => Numeric::F64(self.to_float().max(other.to_float())),
238        }
239    }
240}
241
242impl Interpolatable for Numeric {
243    fn interpolate(&self, other: &Self, t: f64) -> Self {
244        Numeric::F64(Into::<f64>::into(self).interpolate(&other.into(), t))
245    }
246}
247
248impl From<Size> for Numeric {
249    fn from(value: Size) -> Self {
250        match value {
251            Size::Pixels(x) | Size::Percent(x) => x,
252            Size::Combined(_, _) => unreachable!("Cannot coerce a combined size to a numeric"),
253        }
254    }
255}