actyx_sdk/types/
fixnum.rs

1use fixed::types::extra::*;
2use fixed::{
3    traits::{FromFixed, ToFixed},
4    FixedI128,
5};
6use serde::{
7    de::{self, Visitor},
8    Deserialize, Deserializer, Serialize, Serializer,
9};
10use std::fmt::{self, Display};
11use std::iter::{Product, Sum};
12use std::ops::{
13    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub,
14    SubAssign,
15};
16
17/// This is a helper type that allows JSON numbers to be decoded in a fashion
18/// suitable for differential dataflow: it provides equality, hashing, and
19/// Abomonation.
20///
21/// If the number does not fit into the range of the fixed-point number, it
22/// will be [bounded to the valid number range](https://docs.rs/fixed/0.5.5/fixed/struct.FixedI128.html#method.saturating_from_num).
23/// `NaN` will be reported as a deserialization error.
24///
25/// > _Note: this example needs the `dataflow` feature to compile_
26///
27/// ```no_run
28/// # #[macro_use] extern crate abomonation_derive;
29/// use actyx_sdk::types::FixNum;
30/// use actyx_sdk::types::fixnum_types::*;
31/// use serde::{Deserialize, Serialize};
32///
33/// #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Abomonation, Hash)]
34/// struct S {
35///     x: FixNum<U5>,
36///     y: Option<FixNum<U10>>,
37/// }
38/// ```
39#[repr(transparent)]
40#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
41pub struct FixNum<T: LeEqU128>(FixedI128<T>);
42
43impl<T: LeEqU128> Not for FixNum<T> {
44    type Output = FixNum<T>;
45    fn not(self) -> Self::Output {
46        Self(self.0.not())
47    }
48}
49
50impl<T: LeEqU128> Neg for FixNum<T> {
51    type Output = FixNum<T>;
52    fn neg(self) -> Self::Output {
53        Self(self.0.neg())
54    }
55}
56
57macro_rules! op1 {
58    ($Op:ident $fun:ident) => {
59        impl<T: LeEqU128> $Op<FixNum<T>> for FixNum<T> {
60            type Output = FixNum<T>;
61            fn $fun(self, other: FixNum<T>) -> Self {
62                Self((self.0).$fun(other.0))
63            }
64        }
65        impl<T: LeEqU128, U: ToFixed> $Op<U> for FixNum<T> {
66            type Output = FixNum<T>;
67            fn $fun(self, other: U) -> Self {
68                Self((self.0).$fun(FixedI128::saturating_from_num(other)))
69            }
70        }
71        impl<T: LeEqU128> $Op<FixNum<T>> for &FixNum<T> {
72            type Output = FixNum<T>;
73            fn $fun(self, other: FixNum<T>) -> FixNum<T> {
74                FixNum((self.0).$fun(other.0))
75            }
76        }
77        impl<T: LeEqU128, U: ToFixed> $Op<U> for &FixNum<T> {
78            type Output = FixNum<T>;
79            fn $fun(self, other: U) -> FixNum<T> {
80                FixNum((self.0).$fun(FixedI128::saturating_from_num(other)))
81            }
82        }
83        impl<T: LeEqU128> $Op<&FixNum<T>> for FixNum<T> {
84            type Output = FixNum<T>;
85            fn $fun(self, other: &FixNum<T>) -> Self {
86                Self((self.0).$fun(other.0))
87            }
88        }
89        impl<T: LeEqU128> $Op<&FixNum<T>> for &FixNum<T> {
90            type Output = FixNum<T>;
91            fn $fun(self, other: &FixNum<T>) -> FixNum<T> {
92                FixNum((self.0).$fun(other.0))
93            }
94        }
95    };
96}
97
98op1!(Add add);
99op1!(Sub sub);
100op1!(Mul mul);
101op1!(Div div);
102op1!(Rem rem);
103
104macro_rules! op2 {
105    ($Op:ident $fun:ident) => {
106        impl<T: LeEqU128> $Op<FixNum<T>> for FixNum<T> {
107            fn $fun(&mut self, other: FixNum<T>) {
108                (&mut self.0).$fun(other.0);
109            }
110        }
111        impl<T: LeEqU128> $Op<&FixNum<T>> for FixNum<T> {
112            fn $fun(&mut self, other: &FixNum<T>) {
113                (&mut self.0).$fun(other.0);
114            }
115        }
116        impl<T: LeEqU128, U: ToFixed> $Op<U> for FixNum<T> {
117            fn $fun(&mut self, other: U) {
118                (&mut self.0).$fun(FixedI128::<T>::saturating_from_num(other));
119            }
120        }
121    };
122}
123
124op2!(AddAssign add_assign);
125op2!(SubAssign sub_assign);
126op2!(MulAssign mul_assign);
127op2!(DivAssign div_assign);
128op2!(RemAssign rem_assign);
129
130macro_rules! op3 {
131    ($Op:ident $fun:ident $OpA:ident $funA:ident) => {
132        impl<T: LeEqU128> $Op<i32> for FixNum<T> {
133            type Output = Self;
134            fn $fun(self, other: i32) -> Self {
135                Self((self.0).$fun(other))
136            }
137        }
138        impl<T: LeEqU128> $Op<i32> for &FixNum<T> {
139            type Output = FixNum<T>;
140            fn $fun(self, other: i32) -> FixNum<T> {
141                FixNum((self.0).$fun(other))
142            }
143        }
144        impl<T: LeEqU128> $OpA<i32> for FixNum<T> {
145            fn $funA(&mut self, other: i32) {
146                (&mut self.0).$funA(other)
147            }
148        }
149    };
150}
151
152op3!(Shl shl ShlAssign shl_assign);
153op3!(Shr shr ShrAssign shr_assign);
154
155impl<T: LeEqU128> Sum for FixNum<T> {
156    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
157        FixNum(FixedI128::<T>::sum(iter.map(|x| x.0)))
158    }
159}
160
161impl<'a, T: 'a + LeEqU128> Sum<&'a FixNum<T>> for FixNum<T> {
162    fn sum<I: Iterator<Item = &'a FixNum<T>>>(iter: I) -> Self {
163        FixNum(FixedI128::<T>::sum(iter.map(|x| x.0)))
164    }
165}
166
167impl<T: LeEqU128> Product for FixNum<T> {
168    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
169        FixNum(FixedI128::<T>::product(iter.map(|x| x.0)))
170    }
171}
172
173impl<'a, T: 'a + LeEqU128> Product<&'a FixNum<T>> for FixNum<T> {
174    fn product<I: Iterator<Item = &'a FixNum<T>>>(iter: I) -> Self {
175        FixNum(FixedI128::<T>::product(iter.map(|x| x.0)))
176    }
177}
178
179impl<T: LeEqU128> From<FixedI128<T>> for FixNum<T> {
180    fn from(x: FixedI128<T>) -> Self {
181        Self(x)
182    }
183}
184
185impl<T: LeEqU128> From<FixNum<T>> for FixedI128<T> {
186    fn from(o: FixNum<T>) -> FixedI128<T> {
187        o.0
188    }
189}
190
191impl<T: LeEqU128> Display for FixNum<T> {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        self.0.fmt(f)
194    }
195}
196
197impl<T: LeEqU128> FixNum<T> {
198    /// Construct a FixNum from a numeric input (like an `f64` or `u64`), possibly panicking.
199    ///
200    /// Will panic when called with `NaN` (because that is _not a number_ and you
201    /// shouldn’t be passing that around).
202    ///
203    /// Will panic when the converted number does not fit into FixNum.
204    ///
205    /// ```rust
206    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
207    /// let a: FixNum<U12> = FixNum::<U12>::panicking(5);
208    /// let b: FixNum<U12> = a + 12;
209    /// assert_eq!(b, FixNum::panicking(17));
210    /// ```
211    pub fn panicking<Src: ToFixed>(src: Src) -> FixNum<T> {
212        FixNum(src.to_fixed())
213    }
214
215    /// Construct a FixNum from a numeric input (like an `f64` or `u64`), returning an option.
216    ///
217    /// Will panic when called with `NaN` (because that is _not a number_ and you
218    /// shouldn’t be passing that around).
219    ///
220    /// ```rust
221    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
222    /// let a = FixNum::<U32>::checked(5);
223    /// assert_eq!(a, Some(FixNum::panicking(5)));
224    /// ```
225    pub fn checked<Src: ToFixed>(src: Src) -> Option<FixNum<T>> {
226        src.checked_to_fixed().map(FixNum)
227    }
228
229    /// Construct a FixNum from a numeric input (like an `f64` or `u64`), saturating on overflow.
230    ///
231    /// Will panic when called with `NaN` (because that is _not a number_ and you
232    /// shouldn’t be passing that around).
233    ///
234    /// ```rust
235    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
236    /// let a: FixNum<U127> = FixNum::<U127>::saturating(5);
237    /// assert_eq!(a, FixNum::saturating(1));
238    /// ```
239    pub fn saturating<Src: ToFixed>(src: Src) -> FixNum<T> {
240        FixNum(src.saturating_to_fixed())
241    }
242
243    /// Construct a FixNum from a numeric input (like an `f64` or `u64`).
244    ///
245    /// Will panic when called with `NaN` (because that is _not a number_ and you
246    /// shouldn’t be passing that around).
247    ///
248    /// ```rust
249    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
250    /// let a: FixNum<U127> = FixNum::<U127>::wrapping(1.3);
251    /// assert_eq!(a, FixNum::panicking(-0.7));
252    /// ```
253    pub fn wrapping<Src: ToFixed>(src: Src) -> FixNum<T> {
254        FixNum(src.wrapping_to_fixed())
255    }
256
257    /// Construct a FixNum from a numeric input (like an `f64` or `u64`) and returns
258    /// also a flag whether an overflow occurred.
259    ///
260    /// Will panic when called with `NaN` (because that is _not a number_ and you
261    /// shouldn’t be passing that around).
262    ///
263    /// ```rust
264    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
265    /// let (a, overflow) = FixNum::<U127>::overflowing(5);
266    /// assert_eq!(a, FixNum::panicking(-1));
267    /// assert!(overflow);
268    /// ```
269    pub fn overflowing<Src: ToFixed>(src: Src) -> (FixNum<T>, bool) {
270        let (f, b) = src.overflowing_to_fixed();
271        (FixNum(f), b)
272    }
273
274    /// Convert to a primitive number type, panicking on overflow if debug assertions are on.
275    ///
276    /// ```rust
277    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
278    /// let f: i8 = FixNum::<U100>::panicking(5).to_num_panicking();
279    /// assert_eq!(f, 5);
280    /// ```
281    pub fn to_num_panicking<Dst: FromFixed>(self) -> Dst {
282        Dst::from_fixed(self.0)
283    }
284
285    /// Convert to a primitive number type, returning an option.
286    ///
287    /// ```rust
288    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
289    /// let f: Option<i8> = FixNum::<U100>::panicking(5).to_num_checked();
290    /// assert_eq!(f, Some(5));
291    ///
292    /// let f: Option<i8> = FixNum::<U100>::panicking(1000).to_num_checked();
293    /// assert_eq!(f, None);
294    /// ```
295    pub fn to_num_checked<Dst: FromFixed>(self) -> Option<Dst> {
296        Dst::checked_from_fixed(self.0)
297    }
298
299    /// Convert to a primitive number type, saturating on overflow.
300    ///
301    /// ```rust
302    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
303    /// let f: i8 = FixNum::<U100>::panicking(130).to_num_saturating();
304    /// assert_eq!(f, 127);
305    /// ```
306    pub fn to_num_saturating<Dst: FromFixed>(self) -> Dst {
307        Dst::saturating_from_fixed(self.0)
308    }
309
310    /// Convert to a primitive number type, wrapping on overflow.
311    ///
312    /// ```rust
313    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
314    /// let f: i8 = FixNum::<U100>::panicking(130).to_num_wrapping();
315    /// assert_eq!(f, -126);
316    /// ```
317    pub fn to_num_wrapping<Dst: FromFixed>(self) -> Dst {
318        Dst::wrapping_from_fixed(self.0)
319    }
320
321    /// Convert to a primitive number type, wrapping on overflow and returning a flag.
322    ///
323    /// ```rust
324    /// # use actyx_sdk::types::{FixNum, fixnum_types::*};
325    /// let (f, overflow) = FixNum::<U100>::panicking(130).to_num_overflowing::<i8>();
326    /// assert_eq!(f, -126);
327    /// assert!(overflow);
328    /// ```
329    pub fn to_num_overflowing<Dst: FromFixed>(self) -> (Dst, bool) {
330        Dst::overflowing_from_fixed(self.0)
331    }
332}
333
334impl<T: LeEqU128> Serialize for FixNum<T> {
335    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
336        self.0.to_num::<f64>().serialize(serializer)
337    }
338}
339
340impl<'de, T: LeEqU128> Deserialize<'de> for FixNum<T> {
341    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<FixNum<T>, D::Error> {
342        use std::marker::PhantomData;
343        struct X<T>(PhantomData<T>);
344
345        impl<'de, T: LeEqU128> Visitor<'de> for X<T> {
346            type Value = FixNum<T>;
347            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
348                formatter.write_str("number")
349            }
350            fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
351                Ok(FixNum::saturating(v))
352            }
353            fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
354                Ok(FixNum::saturating(v))
355            }
356            fn visit_f64<E: de::Error>(self, v: f64) -> Result<Self::Value, E> {
357                if v.is_nan() {
358                    Err(E::custom("FixNum cannot parse NaN"))
359                } else {
360                    Ok(FixNum::saturating(v))
361                }
362            }
363        }
364
365        deserializer.deserialize_any(X(PhantomData))
366    }
367}
368
369#[cfg(feature = "dataflow")]
370impl<T: LeEqU128> abomonation::Abomonation for FixNum<T> {}
371
372#[cfg(test)]
373#[allow(clippy::unusual_byte_groupings)]
374mod tests {
375    use super::*;
376
377    #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
378    #[cfg_attr(feature = "dataflow", derive(Abomonation))]
379    struct S {
380        x: FixNum<U5>,
381        y: Option<FixNum<U10>>,
382    }
383
384    #[test]
385    pub fn must_serde() {
386        #[allow(clippy::approx_constant)]
387        let js = json!({"x": 6213412, "y": 3.1415926});
388        let s = serde_json::from_value::<S>(js).unwrap();
389        assert_eq!(s.x, FixNum::panicking(6213412));
390        assert_eq!(s.y.unwrap(), FixedI128::<U10>::from_bits(0b11__00100_10001).into());
391        assert_eq!(
392            s.x + FixedI128::<U5>::from_num(0.1),
393            FixedI128::<U5>::from_bits(0b101_11101_10011_11001_00100__00011).into()
394        );
395        let out = serde_json::to_string(&s).unwrap();
396        let s2 = serde_json::from_str::<S>(out.as_str()).unwrap();
397        assert_eq!(s2, s);
398    }
399
400    // this is incorrect on non-x86_64 targets; when encountering this, add a target-conditional
401    // implementation of this function
402    #[cfg(feature = "dataflow")]
403    #[rustfmt::skip]
404    fn expected_bits() -> Vec<u8> {
405        if cfg!(target_arch = "x86_64") {
406            vec![
407                0b100__00011, 0b1_11001_00, 0b1101_1001, 0b101_1, 0, 0, 0, 0, //
408                0, 0, 0, 0, 0, 0, 0, 0, //
409                1, 0, 0, 0, 0, 0, 0, 0, // Option == Some
410                0b100_10001, 0b11__00, 0, 0, 0, 0, 0, 0, //
411                0, 0, 0, 0, 0, 0, 0, 0, //
412            ]
413        } else if cfg!(target_arch = "aarch64") {
414            vec![
415                0b100__00011, 0b1_11001_00, 0b1101_1001, 0b101_1, 0, 0, 0, 0, //
416                0, 0, 0, 0, 0, 0, 0, 0, //
417                1, 0, 0, 0, 0, 0, 0, 0, // Option == Some
418                0, 0, 0, 0, 0, 0, 0, 0, //
419                0b100_10001, 0b11__00, 0, 0, 0, 0, 0, 0, //
420                0, 0, 0, 0, 0, 0, 0, 0, //
421            ]
422        } else {
423            panic!("unsupported CPU architecture")
424        }
425    }
426
427    #[test]
428    #[cfg(feature = "dataflow")]
429    pub fn must_abomonate() {
430        let mut s = S {
431            x: FixNum(FixedI128::<U5>::from_bits(0b101_11101_10011_11001_00100__00011)),
432            y: Some(FixNum(FixedI128::<U10>::from_bits(0b11__00100_10001))),
433        };
434        let mut bytes = Vec::new();
435        unsafe { abomonation::encode(&s, &mut bytes).unwrap() };
436        assert_eq!(bytes, expected_bits());
437        bytes[0] += 64;
438        assert_eq!(
439            s.x,
440            FixedI128::<U5>::from_bits(0b101_11101_10011_11001_00100__00011).into()
441        );
442        s.x += 2;
443        let (value, bytes) = unsafe { abomonation::decode::<S>(&mut bytes) }.unwrap();
444        assert_eq!(value, &s);
445        assert!(bytes.is_empty());
446    }
447
448    fn get_value(v: serde_json::Value) -> FixNum<U110> {
449        serde_json::from_value(v).unwrap()
450    }
451
452    fn get_error(v: serde_json::Value) -> String {
453        serde_json::from_value::<FixNum<U110>>(v).unwrap_err().to_string()
454    }
455
456    #[test]
457    pub fn must_handle_edge_cases() {
458        assert_eq!(get_error(json!({})), "invalid type: map, expected number");
459        let max = FixNum::<U110>::saturating(262144);
460        assert!(max > FixNum::<U110>::panicking(131071.9999999));
461        assert_eq!(get_value(json!(1000000)), max);
462        let min = FixNum::<U110>::saturating(-262144);
463        assert!(min < FixNum::<U110>::panicking(-131071.9999999));
464        assert_eq!(get_value(json!(-1000000)), min);
465    }
466
467    #[test]
468    #[allow(clippy::eq_op)]
469    pub fn must_compute() {
470        use crate::types::fixnum_types::*;
471
472        let mut x = FixNum::<U30>::panicking(12);
473        assert_eq!(x + 3, FixNum::panicking(15));
474        assert_eq!(x + x, FixNum::panicking(24));
475        assert_eq!(x - 3, FixNum::panicking(9));
476        assert_eq!(x - x, FixNum::panicking(0));
477        assert_eq!(x * 3, FixNum::panicking(36));
478        assert_eq!(x * x, FixNum::panicking(144));
479        assert_eq!(x / 4, FixNum::panicking(3));
480        assert_eq!(x / x, FixNum::panicking(1));
481        assert_eq!(x >> 3, FixNum::panicking(1.5));
482        assert_eq!(x << 1, FixNum::panicking(24));
483        assert_eq!(x % 5, FixNum::panicking(2));
484        assert_eq!(x % FixNum::panicking(7), FixNum::panicking(5));
485
486        x += -5.5;
487        x -= FixNum::panicking(2);
488        x /= 8;
489        x *= FixNum::panicking(1.75);
490        x %= FixNum::panicking(3);
491        x >>= 5;
492        x <<= 8;
493
494        assert_eq!(x, FixNum::panicking(63) / FixNum::panicking(8));
495        assert_eq!(!x, FixNum::panicking(-7.875000001));
496        assert_eq!(-x, FixNum::panicking(-63) >> 3);
497
498        let v: Vec<FixNum<U10>> = vec![FixNum::panicking(1), FixNum::panicking(2), FixNum::panicking(3)];
499        assert_eq!(v.iter().sum::<FixNum<U10>>(), FixNum::panicking(6));
500        assert_eq!(v.clone().into_iter().sum::<FixNum<U10>>(), FixNum::panicking(6));
501        assert_eq!(v.iter().product::<FixNum<U10>>(), FixNum::panicking(6));
502        assert_eq!(v.clone().into_iter().product::<FixNum<U10>>(), FixNum::panicking(6));
503
504        assert_eq!(v.iter().map(|x| x + 3).sum::<FixNum<U10>>(), FixNum::panicking(15));
505    }
506}