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