1#[derive(Debug, Clone, Copy, Default, serde::Serialize, serde::Deserialize)]
8#[serde(transparent)]
9pub struct F64Ord(pub f64);
10
11impl F64Ord {
12 pub const fn new(value: f64) -> Self {
13 Self(value)
14 }
15
16 pub const fn zero() -> Self {
17 Self(0.0)
18 }
19
20 pub const fn as_f64(&self) -> f64 {
21 self.0
22 }
23}
24
25impl From<f64> for F64Ord {
26 fn from(value: f64) -> Self {
27 Self(value)
28 }
29}
30
31impl From<F64Ord> for f64 {
32 fn from(value: F64Ord) -> Self {
33 value.0
34 }
35}
36
37impl PartialEq for F64Ord {
38 fn eq(&self, other: &Self) -> bool {
39 self.0.to_bits() == other.0.to_bits()
40 }
41}
42
43impl Eq for F64Ord {}
44
45impl PartialOrd for F64Ord {
46 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
47 Some(self.cmp(other))
48 }
49}
50
51impl Ord for F64Ord {
52 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
53 self.0.total_cmp(&other.0)
54 }
55}
56
57impl core::hash::Hash for F64Ord {
58 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
59 self.0.to_bits().hash(state);
60 }
61}
62
63impl core::fmt::Display for F64Ord {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 self.0.fmt(f)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 use std::cmp::Ordering;
74 use std::collections::hash_map::DefaultHasher;
75 use std::hash::{Hash, Hasher};
76
77 #[test]
78 fn f64ord_should_use_bitwise_equality() {
79 let nan = F64Ord::new(f64::NAN);
80 assert_eq!(nan, F64Ord::new(f64::NAN)); assert_eq!(F64Ord::new(-0.0).cmp(&F64Ord::new(0.0)), Ordering::Less);
83 assert_ne!(F64Ord::new(-0.0), F64Ord::new(0.0));
84
85 let mut h1 = DefaultHasher::new();
86 let mut h2 = DefaultHasher::new();
87 nan.hash(&mut h1);
88 nan.hash(&mut h2);
89 assert_eq!(h1.finish(), h2.finish());
90 }
91}
92
93pub use core::ops::{Add, Div, Mul, Sub};
95
96pub trait NumericLiteral {
98 fn literal_from_f64(value: f64) -> Self;
99 fn literal_from_str(value: &str) -> Result<Self, String>
100 where
101 Self: Sized;
102}
103
104impl NumericLiteral for f64 {
105 fn literal_from_f64(value: f64) -> Self {
106 value
107 }
108
109 fn literal_from_str(value: &str) -> Result<Self, String> {
110 value
111 .parse()
112 .map_err(|e| format!("Failed to parse f64: {}", e))
113 }
114}
115
116pub trait NumericOps: Copy + Clone + PartialOrd + PartialEq {
118 fn checked_add(&self, other: Self) -> Option<Self>;
119 fn checked_sub(&self, other: Self) -> Option<Self>;
120 fn checked_mul(&self, other: Self) -> Option<Self>;
121 fn checked_div(&self, other: Self) -> Option<Self>;
122 fn checked_rem(&self, other: Self) -> Option<Self>;
123 fn saturating_add(&self, other: Self) -> Self;
124 fn saturating_sub(&self, other: Self) -> Self;
125 fn saturating_mul(&self, other: Self) -> Self;
126 fn round_2dp(self) -> Self;
127 fn zero() -> Self;
128 fn is_sign_positive(&self) -> bool;
129 fn is_sign_negative(&self) -> bool;
130}
131
132impl NumericOps for f64 {
133 fn checked_add(&self, other: Self) -> Option<Self> {
134 let result = self + other;
135 if result.is_finite() {
136 Some(result)
137 } else {
138 None
139 }
140 }
141
142 fn checked_sub(&self, other: Self) -> Option<Self> {
143 let result = self - other;
144 if result.is_finite() {
145 Some(result)
146 } else {
147 None
148 }
149 }
150
151 fn checked_mul(&self, other: Self) -> Option<Self> {
152 let result = self * other;
153 if result.is_finite() {
154 Some(result)
155 } else {
156 None
157 }
158 }
159
160 fn checked_div(&self, other: Self) -> Option<Self> {
161 if other == 0.0 {
162 None
163 } else {
164 let result = self / other;
165 if result.is_finite() {
166 Some(result)
167 } else {
168 None
169 }
170 }
171 }
172
173 fn checked_rem(&self, other: Self) -> Option<Self> {
174 if other == 0.0 {
175 None
176 } else {
177 let result = self % other;
178 if result.is_finite() {
179 Some(result)
180 } else {
181 None
182 }
183 }
184 }
185
186 fn saturating_add(&self, other: Self) -> Self {
187 let result = self + other;
188 if result.is_finite() {
189 result
190 } else if result.is_infinite() && result.is_sign_positive() {
191 f64::MAX
192 } else {
193 f64::MIN
194 }
195 }
196
197 fn saturating_sub(&self, other: Self) -> Self {
198 let result = self - other;
199 if result.is_finite() {
200 result
201 } else if result.is_infinite() && result.is_sign_positive() {
202 f64::MAX
203 } else {
204 f64::MIN
205 }
206 }
207
208 fn saturating_mul(&self, other: Self) -> Self {
209 let result = self * other;
210 if result.is_finite() {
211 result
212 } else if result.is_infinite() && result.is_sign_positive() {
213 f64::MAX
214 } else {
215 f64::MIN
216 }
217 }
218
219 #[inline(always)]
220 fn round_2dp(self) -> Self {
221 (self * 100.0).round() / 100.0
222 }
223
224 fn zero() -> Self {
225 0.0
226 }
227
228 fn is_sign_positive(&self) -> bool {
229 f64::is_sign_positive(*self)
230 }
231
232 fn is_sign_negative(&self) -> bool {
233 f64::is_sign_negative(*self)
234 }
235}
236
237#[macro_export]
239macro_rules! num {
240 ($lit:literal) => {{ $lit as f64 }};
241}
242
243#[macro_export]
245macro_rules! num_ord {
246 ($lit:literal) => {{ $crate::numeric::F64Ord::from($lit as f64) }};
247}
248
249#[macro_export]
251macro_rules! num_u8 {
252 ($lit:literal) => {{ $lit as u8 }};
253}