1use std::cmp::Ordering;
2use std::fmt::{Debug, Display, Formatter};
3use std::hash::{Hash, Hasher};
4use std::num::FpCategory::{Infinite, Nan};
5use std::ops::Deref;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, PartialEq, Eq)]
12pub enum ErrorTryFrom {
13 CannotFixNan,
15 CannotFixInfinity,
17}
18
19macro_rules! _impl_ty {
20 ($base:ty, $new:ident) => {
21 doc_comment! {
22 concat!(
23 "Abstract wrapper for fix ", stringify!($base), ", or ", stringify!($new), " for short.
24
25This wrapper implements:
26 - Default
27 - Copy
28 - Clone
29 - Debug
30 - Display
31 - PartialEq
32 - Eq
33 - PartialOrd
34 - Ord
35 - Hash
36 - Deref<Target = ", stringify!($base), ">
37 - TryFrom", "<", stringify!($base), ">
38
39```
40# use fix_float::*;
41# fn main() {
42 let x: ", stringify!($base), " = 42.42;
43 let a: ", stringify!($new), " = ", stringify!($new), "::try_from(x).unwrap();
44 let b: ", stringify!($new), " = x.try_into().unwrap();
45 let c: ", stringify!($new), " = ", stringify!($new), "!(x);
46# }
47```
48",
49 ),
50
51 #[allow(non_camel_case_types)]
52 #[derive(Default, Clone, Copy)]
53 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54 pub struct $new {
55 x: $base,
56 }
57
58 impl $new {
59 #[inline]
60 fn is_fixable(x: $base) -> Option<ErrorTryFrom> {
61 match x.classify() {
62 Nan => Some(ErrorTryFrom::CannotFixNan),
63 Infinite => Some(ErrorTryFrom::CannotFixInfinity),
64 _ => None,
65 }
66 }
67
68 #[inline]
69 fn try_from(x: $base) -> Result<$new, ErrorTryFrom> {
70 match Self::is_fixable(x) {
71 Some(err) => Err(err),
72 None => Ok($new {
73 x: if (x == (0.0 as $base)) {
74 0.0 as $base
75 } else {
76 x
77 },
78 }),
79 }
80 }
81
82 #[inline]
83 fn my_partial_cmp(lhs: &$new, rhs: &$new) -> Option<Ordering> {
84 Some(lhs.x.total_cmp(&rhs.x))
85 }
86
87 #[inline]
88 fn my_cmp(lhs: &$new, rhs: &$new) -> Ordering {
89 lhs.x.total_cmp(&rhs.x)
90 }
91
92 #[inline]
93 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94 f.pad(&format!("fix {}", self.x))
95 }
96 }
97
98 impl PartialEq for $new {
99 #[inline]
100 #[must_use]
101 fn eq(&self, other: &Self) -> bool {
102 self.x == other.x
103 }
104 }
105
106 impl Eq for $new {}
107
108 impl PartialOrd for $new {
109 #[inline]
110 #[must_use]
111 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112 Self::my_partial_cmp(self, other)
113 }
114 }
115
116 impl Ord for $new {
117 #[inline]
118 #[must_use]
119 fn cmp(&self, other: &Self) -> Ordering {
120 Self::my_cmp(self, other)
121 }
122 }
123
124 impl Debug for $new {
125 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126 self.fmt(f)
127 }
128 }
129
130 impl Display for $new {
131 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
132 self.fmt(f)
133 }
134 }
135
136 impl Hash for $new {
137 fn hash<H: Hasher>(&self, state: &mut H) {
138 self.x.to_bits().hash(state)
139 }
140 }
141
142 impl From<$new> for $base {
143 #[inline]
144 #[must_use]
145 fn from(x: $new) -> Self {
146 x.x
147 }
148 }
149
150 impl TryFrom<$base> for $new {
151 type Error = ErrorTryFrom;
152
153 #[inline]
154 fn try_from(value: $base) -> Result<Self, Self::Error> {
155 Self::try_from(value)
156 }
157 }
158
159 impl Deref for $new {
160 type Target = $base;
161
162 #[inline]
163 #[must_use]
164 fn deref(&self) -> &Self::Target {
165 &self.x
166 }
167 }
168
169 doc_comment!{
170 concat!("Macro that does try_from and unwraps it.
171
172`", stringify!($new), "!(x)` <=> `fix_float::", stringify!($new), "::try_from(x).unwrap()`
173
174You should use this macro when you are sure that you are not having Nan or Infinity. Otherwise, it will panic.
175
176 "),
177 #[macro_export]
178 macro_rules! $new {
179 ($x:literal) => {
180 $crate::$new::try_from($x).unwrap()
181 };
182 ($x:expr) => {
183 $crate::$new::try_from($x).unwrap()
184 };
185 }
186 }
187 }
188 };
189}
190
191_impl_ty!(f64, ff64);
192_impl_ty!(f32, ff32);
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 const F64_COMMON_FLOATS: &[f64] = &[
199 0.0,
200 1.0,
201 -1.0,
202 42.42,
203 f64::EPSILON,
204 f64::MIN,
205 f64::MAX,
206 std::f64::consts::E,
207 std::f64::consts::FRAC_1_PI,
208 std::f64::consts::FRAC_1_SQRT_2,
209 std::f64::consts::FRAC_2_PI,
210 std::f64::consts::FRAC_2_SQRT_PI,
211 std::f64::consts::FRAC_PI_2,
212 std::f64::consts::FRAC_PI_3,
213 std::f64::consts::FRAC_PI_4,
214 std::f64::consts::FRAC_PI_6,
215 std::f64::consts::FRAC_PI_8,
216 std::f64::consts::LN_2,
217 std::f64::consts::LN_10,
218 std::f64::consts::LOG2_10,
219 std::f64::consts::LOG2_E,
220 std::f64::consts::LOG10_2,
221 std::f64::consts::LOG10_E,
222 std::f64::consts::PI,
223 std::f64::consts::SQRT_2,
224 std::f64::consts::TAU,
225 ];
226
227 const F32_COMMON_FLOATS: &[f32] = &[
228 0.0,
229 1.0,
230 -1.0,
231 42.42,
232 f32::EPSILON,
233 f32::MIN,
234 f32::MAX,
235 std::f32::consts::E,
236 std::f32::consts::FRAC_1_PI,
237 std::f32::consts::FRAC_1_SQRT_2,
238 std::f32::consts::FRAC_2_PI,
239 std::f32::consts::FRAC_2_SQRT_PI,
240 std::f32::consts::FRAC_PI_2,
241 std::f32::consts::FRAC_PI_3,
242 std::f32::consts::FRAC_PI_4,
243 std::f32::consts::FRAC_PI_6,
244 std::f32::consts::FRAC_PI_8,
245 std::f32::consts::LN_2,
246 std::f32::consts::LN_10,
247 std::f32::consts::LOG2_10,
248 std::f32::consts::LOG2_E,
249 std::f32::consts::LOG10_2,
250 std::f32::consts::LOG10_E,
251 std::f32::consts::PI,
252 std::f32::consts::SQRT_2,
253 std::f32::consts::TAU,
254 ];
255
256 #[test]
257 fn ff64_common_floats() {
258 for &x in F64_COMMON_FLOATS {
259 let fx = ff64!(x);
260 assert_eq!(x, *fx)
261 }
262 }
263
264 #[test]
265 #[should_panic]
266 fn ff64_nan() {
267 let _ = ff64!(f64::NAN);
268 }
269
270 #[test]
271 #[should_panic]
272 fn ff64_inf() {
273 let _ = ff64!(f64::INFINITY);
274 }
275
276 #[test]
277 #[should_panic]
278 fn ff64_neg_inf() {
279 let _ = ff64!(f64::NEG_INFINITY);
280 }
281
282 #[test]
283 fn ff32_common_floats() {
284 for &x in F32_COMMON_FLOATS {
285 let fx = ff32!(x);
286 assert_eq!(x, *fx)
287 }
288 }
289
290 #[test]
291 #[should_panic]
292 fn ff32_nan() {
293 let _ = ff32!(f32::NAN);
294 }
295
296 #[test]
297 #[should_panic]
298 fn ff32_inf() {
299 let _ = ff32!(f32::INFINITY);
300 }
301
302 #[test]
303 #[should_panic]
304 fn ff32_neg_inf() {
305 let _ = ff32!(f32::NEG_INFINITY);
306 }
307
308 #[test]
309 fn default_ff64() {
310 let a = ff64::default();
311
312 assert_eq!(f64::from(a), f64::default());
313 }
314
315 #[test]
316 fn default_ff32() {
317 let a = ff32::default();
318
319 assert_eq!(f32::from(a), f32::default());
320 }
321
322 #[test]
323 fn mem_f64() {
324 assert_eq!(std::mem::size_of::<f64>(), std::mem::size_of::<ff64>())
325 }
326
327 #[test]
328 fn mem_f32() {
329 assert_eq!(std::mem::size_of::<f32>(), std::mem::size_of::<ff32>())
330 }
331
332 #[test]
333 fn eq_zero() {
334 let a = ff64!(0.0);
335 let b = ff64!(0.0);
336 let c = ff64!(-0.0);
337
338 assert_eq!(a, b);
339 assert_eq!(a, c);
340 }
341
342 #[test]
343 fn eq_fuzzy() {
344 for _ in 0..10000 {
345 let random_float = rand::random();
346 let a = ff64!(random_float);
347 let b = ff64!(random_float);
348
349 assert_eq!(a, b);
350 }
351 }
352
353 #[test]
354 fn ord_fuzzy() {
355 for _ in 0..10000 {
356 let a = rand::random();
357 let b = rand::random();
358 let fa = ff64!(a);
359 let fb = ff64!(b);
360
361 assert_eq!(a.partial_cmp(&b), fa.partial_cmp(&fb));
362 assert_eq!(a.partial_cmp(&b).unwrap(), fa.cmp(&fb));
363 }
364 }
365
366 #[test]
367 fn fmt() {
368 assert_eq!(format!("{}", ff64!(2.42)), "fix 2.42");
369 assert_eq!(format!("{:?}", ff64!(2.42)), "fix 2.42");
370 assert_eq!(format!("{:}", ff64!(2.42)), "fix 2.42");
371 assert_eq!(format!("{:10}", ff64!(2.42)), "fix 2.42 ");
372 assert_eq!(format!("{:15}", ff64!(2.42)), "fix 2.42 ");
373 assert_eq!(format!("{:0<15}", ff64!(2.42)), "fix 2.420000000");
374 assert_eq!(format!("{:>15}", ff64!(2.42)), " fix 2.42");
375 assert_eq!(format!("{:0>15}", ff64!(2.42)), "0000000fix 2.42");
376 }
377
378 #[test]
379 fn is_send() {
380 fn assert_send<T: Send>() {}
381
382 assert_send::<ff64>();
383 assert_send::<ff32>();
384 }
385
386 #[test]
387 fn is_sync() {
388 fn assert_sync<T: Sync>() {}
389
390 assert_sync::<ff64>();
391 assert_sync::<ff32>();
392 }
393
394 #[test]
395 fn try_from() {
396 let a: ff64 = ff64::try_from(42.42).unwrap();
397 let b: ff64 = (42.42).try_into().unwrap();
398 let c: ff64 = ff64!(42.42);
399
400 assert_eq!(a, b);
401 assert_eq!(a, c);
402
403 assert_eq!(*a, *b);
404 assert_eq!(*a, *c);
405 }
406
407 #[test]
408 fn try_from_nan() {
409 let a = ff64::try_from(f64::NAN);
410 assert!(a.is_err());
411 assert_eq!(a.unwrap_err(), ErrorTryFrom::CannotFixNan);
412 }
413
414 #[test]
415 fn try_from_inf() {
416 let a = ff64::try_from(f64::INFINITY);
417 assert!(a.is_err());
418 assert_eq!(a.unwrap_err(), ErrorTryFrom::CannotFixInfinity);
419
420 let b = ff64::try_from(f64::NEG_INFINITY);
421 assert!(b.is_err());
422 assert_eq!(b.unwrap_err(), ErrorTryFrom::CannotFixInfinity);
423 }
424
425 #[test]
426 #[cfg(feature = "serde")]
427 fn serde_json() {
428 let a = ff64!(42.42);
429
430 let json_a = serde_json::to_string(&a).unwrap();
431 let a_json_a: ff64 = serde_json::from_str(&json_a).unwrap();
432
433 assert_eq!(json_a, "{\"x\":42.42}");
434 assert_eq!(a, a_json_a);
435 }
436}