Skip to main content

ffl/
fixed.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::fixed_format::{ConvertValue, Value};
6use crate::num_traits;
7
8/// A fixed-point number represented by an underlying integer type and a
9/// fractional bit depth.
10///
11/// `Fixed` provides precise, saturating arithmetic operations with convergent
12/// rounding.
13///
14/// # Generics
15///
16/// - `I`: The underlying integer type (e.g. `i32`, `i64`).
17/// - `FRAC`: The number of bits reserved for the fractional component.
18#[repr(transparent)]
19#[derive(Default, Clone, Copy, Hash)]
20pub struct Fixed<I, const FRAC: usize> {
21    value: I,
22}
23
24impl<I: Copy, const FRAC: usize> Fixed<I, FRAC> {
25    const _ASSERT_VALID_FORMAT: () = {
26        let bits = core::mem::size_of::<I>() * 8;
27        assert!(bits <= 64, "The Integer template parameter must have at most 64 bits!");
28        assert!(FRAC <= bits, "The number of fractional bits cannot exceed the integer bit-width!");
29    };
30
31    /// Returns the given raw integer as a fixed-point value in this format.
32    pub const fn from_raw(value: I) -> Self {
33        let () = Self::_ASSERT_VALID_FORMAT;
34        Self { value }
35    }
36
37    /// Returns the raw fixed-point value as the underlying integer type.
38    pub const fn raw_value(&self) -> I {
39        self.value
40    }
41
42    /// Returns the fixed-point value as an intermediate value type.
43    pub const fn value(&self) -> Value<I, FRAC> {
44        Value { value: self.value }
45    }
46
47    /// Returns a Fixed value representing the ratio between numerator and denominator.
48    pub fn from_ratio(numerator: I, denominator: I) -> Self
49    where
50        I: num_traits::Bounded + num_traits::NumCast + Copy,
51        Value<I, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
52    {
53        let expr = crate::expression::from_ratio(numerator, denominator);
54        Fixed::from(expr)
55    }
56
57    /// Multiplies self with another Fixed value and scales directly to target resolution.
58    pub fn mul_to<U, const FRAC_R: usize, R, const FRAC_OUT: usize>(
59        self,
60        other: Fixed<U, FRAC_R>,
61    ) -> Fixed<R, FRAC_OUT>
62    where
63        I: Into<i128> + Copy,
64        U: Into<i128> + Copy,
65        R: num_traits::Bounded + num_traits::NumCast + Copy,
66    {
67        let val_l: i128 = self.value.into();
68        let val_r: i128 = other.value.into();
69
70        let neg = (val_l < 0) ^ (val_r < 0);
71        let a_abs = val_l.unsigned_abs();
72        let b_abs = val_r.unsigned_abs();
73        let prod_abs = a_abs.saturating_mul(b_abs);
74
75        let src_frac = FRAC + FRAC_R;
76        let dst_frac = FRAC_OUT;
77
78        let scaled_abs = if src_frac > dst_frac {
79            let delta = src_frac - dst_frac;
80            let rounded = crate::utility::round_half_to_even::<u128>(prod_abs, delta);
81            if let Some(power) = 1u128.checked_shl(delta as u32) { rounded / power } else { 0 }
82        } else if src_frac < dst_frac {
83            let delta = dst_frac - src_frac;
84            if prod_abs <= (u128::MAX >> delta) { prod_abs << delta } else { u128::MAX }
85        } else {
86            prod_abs
87        };
88
89        // Convert the scaled absolute value back to signed (if necessary) and then to R
90        let converted: R = if neg {
91            let scaled_i128 = if scaled_abs > i128::MIN.unsigned_abs() {
92                i128::MIN
93            } else {
94                (scaled_abs as i128).wrapping_neg()
95            };
96            crate::utility::clamp_cast(scaled_i128)
97        } else {
98            crate::utility::clamp_cast_u128(scaled_abs)
99        };
100
101        Fixed::from_raw(converted)
102    }
103}
104
105impl<I: num_traits::Bounded + num_traits::NumCast + Copy, const FRAC: usize> Fixed<I, FRAC> {
106    /// Returns the minimum value of this fixed point format.
107    pub fn min() -> Self {
108        Self::from_raw(I::min_value())
109    }
110
111    /// Returns the maximum value of this fixed point format.
112    pub fn max() -> Self {
113        Self::from_raw(I::max_value())
114    }
115
116    /// Returns the smallest positive value of this fixed point format.
117    pub fn epsilon() -> Self
118    where
119        I: num_traits::One,
120    {
121        Self::from_raw(I::one())
122    }
123
124    /// Explicit conversion from an integer value.
125    pub fn from_integer(val: i128) -> Self
126    where
127        I: num_traits::Bounded + num_traits::NumCast + Copy,
128        Value<I, 0>: crate::fixed_format::ConvertValue<I, FRAC>,
129    {
130        let val_i: I = crate::utility::clamp_cast(val);
131        Self::from(Value::<I, 0> { value: val_i }.convert_value())
132    }
133
134    /// Explicit conversion from another fixed-point format.
135    pub fn from_fixed<OtherI: Copy, const OTHER_FRAC: usize>(
136        other: Fixed<OtherI, OTHER_FRAC>,
137    ) -> Self
138    where
139        Value<OtherI, OTHER_FRAC>: crate::fixed_format::ConvertValue<I, FRAC>,
140    {
141        Self::from(other.value().convert_value())
142    }
143}
144
145impl<I: Copy, const FRAC: usize> From<Value<I, FRAC>> for Fixed<I, FRAC> {
146    fn from(val: Value<I, FRAC>) -> Self {
147        Self::from_raw(val.value)
148    }
149}
150
151#[inline]
152fn safe_shl_u128(val: u128, shift: usize) -> Option<u128> {
153    if val == 0 {
154        return Some(0);
155    }
156    if shift >= 128 {
157        None
158    } else {
159        if val <= (u128::MAX >> shift) { Some(val << shift) } else { None }
160    }
161}
162
163#[inline]
164fn safe_shl_i128(val: i128, shift: usize) -> Option<i128> {
165    if val == 0 {
166        return Some(0);
167    }
168    if val > 0 {
169        if shift >= 127 {
170            None
171        } else {
172            if (val as u128) <= ((i128::MAX as u128) >> shift) { Some(val << shift) } else { None }
173        }
174    } else {
175        let abs = val.unsigned_abs();
176        let safe = if shift >= 128 { false } else { abs <= (1u128 << (127 - shift)) };
177        if safe { Some(val << shift) } else { None }
178    }
179}
180
181pub(crate) fn compare_raw(
182    lhs_raw: i128,
183    f1: usize,
184    rhs_raw: i128,
185    f2: usize,
186) -> core::cmp::Ordering {
187    if lhs_raw < 0 && rhs_raw >= 0 {
188        return core::cmp::Ordering::Less;
189    }
190    if lhs_raw >= 0 && rhs_raw < 0 {
191        return core::cmp::Ordering::Greater;
192    }
193    if f1 == f2 {
194        return lhs_raw.cmp(&rhs_raw);
195    }
196    if lhs_raw >= 0 {
197        let l = lhs_raw as u128;
198        let r = rhs_raw as u128;
199        if f1 > f2 {
200            if let Some(val) = safe_shl_u128(r, f1 - f2) {
201                l.cmp(&val)
202            } else {
203                core::cmp::Ordering::Less
204            }
205        } else {
206            if let Some(val) = safe_shl_u128(l, f2 - f1) {
207                val.cmp(&r)
208            } else {
209                core::cmp::Ordering::Greater
210            }
211        }
212    } else {
213        if f1 > f2 {
214            if let Some(val) = safe_shl_i128(rhs_raw, f1 - f2) {
215                lhs_raw.cmp(&val)
216            } else {
217                core::cmp::Ordering::Greater
218            }
219        } else {
220            if let Some(val) = safe_shl_i128(lhs_raw, f2 - f1) {
221                val.cmp(&rhs_raw)
222            } else {
223                core::cmp::Ordering::Less
224            }
225        }
226    }
227}
228
229// Relational operators between different Fixed types (fully generic, using
230// shift-based math to avoid generic const bounds)
231impl<I, const F1: usize, OtherI, const F2: usize> PartialEq<Fixed<OtherI, F2>> for Fixed<I, F1>
232where
233    I: Into<i128> + Copy,
234    OtherI: Into<i128> + Copy,
235{
236    fn eq(&self, other: &Fixed<OtherI, F2>) -> bool {
237        let lhs_i: i128 = self.raw_value().into();
238        let rhs_i: i128 = other.raw_value().into();
239        compare_raw(lhs_i, F1, rhs_i, F2) == core::cmp::Ordering::Equal
240    }
241}
242
243impl<I, const F1: usize, OtherI, const F2: usize> PartialOrd<Fixed<OtherI, F2>> for Fixed<I, F1>
244where
245    I: Into<i128> + Copy,
246    OtherI: Into<i128> + Copy,
247{
248    fn partial_cmp(&self, other: &Fixed<OtherI, F2>) -> Option<core::cmp::Ordering> {
249        let lhs_i: i128 = self.raw_value().into();
250        let rhs_i: i128 = other.raw_value().into();
251        Some(compare_raw(lhs_i, F1, rhs_i, F2))
252    }
253}
254
255/// Compares two fixed-point numbers with potentially different underlying types
256/// and fractional resolutions.
257pub fn compare_fixed<I1, const F1: usize, I2, const F2: usize>(
258    lhs: Fixed<I1, F1>,
259    rhs: Fixed<I2, F2>,
260) -> core::cmp::Ordering
261where
262    I1: Into<i128> + Copy,
263    I2: Into<i128> + Copy,
264{
265    let lhs_i: i128 = lhs.raw_value().into();
266    let rhs_i: i128 = rhs.raw_value().into();
267    compare_raw(lhs_i, F1, rhs_i, F2)
268}
269
270// Relational operators between Fixed and integers
271macro_rules! impl_fixed_integer_comparison {
272    ($T:ty, $Int:ty) => {
273        impl<const FRAC: usize> PartialEq<$Int> for Fixed<$T, FRAC>
274        where
275            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
276            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
277        {
278            fn eq(&self, other: &$Int) -> bool {
279                let lhs: Value<i128, FRAC> = self.value().convert_value();
280                let rhs: Value<i128, FRAC> = Value::<$Int, 0> { value: *other }.convert_value();
281                lhs.value == rhs.value
282            }
283        }
284
285        impl<const FRAC: usize> PartialEq<Fixed<$T, FRAC>> for $Int
286        where
287            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
288            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
289        {
290            fn eq(&self, other: &Fixed<$T, FRAC>) -> bool {
291                other.eq(self)
292            }
293        }
294
295        impl<const FRAC: usize> PartialOrd<$Int> for Fixed<$T, FRAC>
296        where
297            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
298            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
299        {
300            fn partial_cmp(&self, other: &$Int) -> Option<core::cmp::Ordering> {
301                let lhs: Value<i128, FRAC> = self.value().convert_value();
302                let rhs: Value<i128, FRAC> = Value::<$Int, 0> { value: *other }.convert_value();
303                lhs.value.partial_cmp(&rhs.value)
304            }
305        }
306
307        impl<const FRAC: usize> PartialOrd<Fixed<$T, FRAC>> for $Int
308        where
309            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
310            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
311        {
312            fn partial_cmp(&self, other: &Fixed<$T, FRAC>) -> Option<core::cmp::Ordering> {
313                other.partial_cmp(self).map(|o| o.reverse())
314            }
315        }
316    };
317}
318
319macro_rules! gen_fixed_integer_comparisons {
320    (signed: [$($T:ty),*], $Int_list:tt) => {
321        $(
322            gen_fixed_integer_comparisons!(@signed_one $T, $Int_list);
323        )*
324    };
325    (@signed_one $T:ty, [$($Int:ty),*]) => {
326        $(
327            impl_fixed_integer_comparison!($T, $Int);
328        )*
329    };
330
331    (unsigned: [$($T:ty),*], $Int_list:tt) => {
332        $(
333            gen_fixed_integer_comparisons!(@unsigned_one $T, $Int_list);
334        )*
335    };
336    (@unsigned_one $T:ty, [$($Int:ty),*]) => {
337        $(
338            impl_fixed_integer_comparison!($T, $Int);
339        )*
340    };
341
342    (mixed: [$($T:ty),*], $Int_list:tt) => {
343        $(
344            gen_fixed_integer_comparisons!(@mixed_one $T, $Int_list);
345        )*
346    };
347    (@mixed_one $T:ty, [$($Int:ty),*]) => {
348        $(
349            impl_fixed_integer_comparison!($T, $Int);
350        )*
351    };
352}
353
354gen_fixed_integer_comparisons!(signed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
355gen_fixed_integer_comparisons!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
356gen_fixed_integer_comparisons!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
357gen_fixed_integer_comparisons!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);
358
359impl<I, const FRAC: usize> Eq for Fixed<I, FRAC> where I: Into<i128> + Copy {}
360
361/// Note: In Rust, `Ord` only permits comparing `Self` with `Self` (same integer type and fractional bit count `FRAC`).
362/// Cross-scale and cross-type comparisons are provided via `PartialOrd`.
363impl<I, const FRAC: usize> Ord for Fixed<I, FRAC>
364where
365    I: Into<i128> + Copy,
366{
367    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
368        compare_fixed(*self, *other)
369    }
370}
371
372// Logical methods on Fixed
373impl<I, const FRAC: usize> Fixed<I, FRAC> {
374    /// Returns the closest integer value greater-than or equal-to this fixed-point value.
375    pub fn ceiling(&self) -> I
376    where
377        I: num_traits::Bounded
378            + num_traits::NumCast
379            + Copy
380            + Into<i128>
381            + num_traits::One
382            + num_traits::Zero,
383    {
384        let raw_i: i128 = self.value.into();
385        let power = if let Some(p) = 1i128.checked_shl(FRAC as u32) {
386            p
387        } else {
388            return if raw_i > 0 { I::one() } else { I::zero() };
389        };
390        let div = raw_i / power;
391        let rem = raw_i % power;
392
393        let res = if rem != 0 && raw_i > 0 { div + 1 } else { div };
394        crate::utility::clamp_cast(res)
395    }
396
397    /// Returns the closest integer value less-than or equal-to this fixed-point value.
398    pub fn floor(&self) -> I
399    where
400        I: num_traits::Bounded + num_traits::NumCast + Copy + Into<i128> + num_traits::Zero,
401    {
402        let raw_i: i128 = self.value.into();
403        let power = if let Some(p) = 1i128.checked_shl(FRAC as u32) {
404            p
405        } else {
406            return if raw_i < 0 { crate::utility::clamp_cast(-1i128) } else { I::zero() };
407        };
408        let div = raw_i / power;
409        let rem = raw_i % power;
410
411        let res = if rem != 0 && raw_i < 0 { div - 1 } else { div };
412        crate::utility::clamp_cast(res)
413    }
414
415    /// Returns the rounded value of this fixed-point value as an integer.
416    pub fn round(&self) -> I
417    where
418        I: num_traits::Bounded + num_traits::NumCast + Copy + Into<i128> + num_traits::Zero,
419    {
420        let raw_i: i128 = self.value.into();
421        let rounded = crate::utility::round_half_to_even::<i128>(raw_i, FRAC);
422        let power = if let Some(p) = 1i128.checked_shl(FRAC as u32) {
423            p
424        } else {
425            return I::zero();
426        };
427        let rounded_int = rounded / power;
428        crate::utility::clamp_cast(rounded_int)
429    }
430
431    /// Returns the integral component of this fixed-point value.
432    pub fn integral(&self) -> Self
433    where
434        I: num_traits::Bounded + num_traits::NumCast + PartialOrd + Copy,
435    {
436        if FRAC == 0 {
437            return *self;
438        }
439        let raw_i: i128 = self.value.into();
440        let mask = (!0u128).checked_shl(FRAC as u32).unwrap_or(0);
441        let abs_integral = raw_i.unsigned_abs() & mask;
442        let raw_integral = if raw_i < 0 { -(abs_integral as i128) } else { abs_integral as i128 };
443        Self::from_raw(crate::utility::clamp_cast(raw_integral))
444    }
445
446    /// Returns the fractional component of this fixed-point value.
447    pub fn fraction(&self) -> Self
448    where
449        I: num_traits::Bounded
450            + num_traits::NumCast
451            + PartialOrd
452            + Copy
453            + num_traits::Zero
454            + core::ops::Sub<Output = I>,
455    {
456        if FRAC == 0 {
457            return Self::from_raw(I::zero());
458        }
459        Self::from_raw(self.value - self.integral().value)
460    }
461
462    /// Returns the absolute value of this fixed-point value.
463    ///
464    /// Note: If the underlying value is the minimum value representable by a signed type
465    /// (e.g., `I::MIN`), its mathematical absolute value cannot be represented and will
466    /// saturate to `I::MAX`.
467    pub fn absolute(&self) -> Self
468    where
469        I: num_traits::Bounded + num_traits::NumCast + Copy + Into<i128>,
470    {
471        let raw_i: i128 = self.value.into();
472        if raw_i >= 0 {
473            return *self;
474        }
475        let abs_raw = raw_i.unsigned_abs();
476        Self::from_raw(crate::utility::clamp_cast_u128(abs_raw))
477    }
478}
479
480// Promotion traits for operators
481pub trait PromoteAddSub<U> {
482    type Output;
483}
484pub trait PromoteMul<U> {
485    type Output;
486}
487
488macro_rules! impl_promotes {
489    ($T:ty, $U:ty => $AddSub:ty, $Mul:ty) => {
490        impl PromoteAddSub<$U> for $T {
491            type Output = $AddSub;
492        }
493        impl PromoteAddSub<$T> for $U {
494            type Output = $AddSub;
495        }
496        impl PromoteMul<$U> for $T {
497            type Output = $Mul;
498        }
499        impl PromoteMul<$T> for $U {
500            type Output = $Mul;
501        }
502    };
503}
504
505macro_rules! impl_promotes_same {
506    ($T:ty => $AddSub:ty, $Mul:ty) => {
507        impl PromoteAddSub<$T> for $T {
508            type Output = $AddSub;
509        }
510        impl PromoteMul<$T> for $T {
511            type Output = $Mul;
512        }
513    };
514}
515
516impl_promotes_same!(u8 => u16, u16);
517impl_promotes_same!(u16 => u32, u32);
518impl_promotes_same!(u32 => u64, u64);
519impl_promotes_same!(u64 => u64, u64);
520
521impl_promotes_same!(i8 => i16, i16);
522impl_promotes_same!(i16 => i32, i32);
523impl_promotes_same!(i32 => i64, i64);
524impl_promotes_same!(i64 => i64, i64);
525
526impl_promotes!(u8, u16 => u32, u32);
527impl_promotes!(u8, u32 => u64, u64);
528impl_promotes!(u8, u64 => u64, u64);
529impl_promotes!(u16, u32 => u64, u64);
530impl_promotes!(u16, u64 => u64, u64);
531impl_promotes!(u32, u64 => u64, u64);
532
533impl_promotes!(i8, i16 => i32, i32);
534impl_promotes!(i8, i32 => i64, i64);
535impl_promotes!(i8, i64 => i64, i64);
536impl_promotes!(i16, i32 => i64, i64);
537impl_promotes!(i16, i64 => i64, i64);
538impl_promotes!(i32, i64 => i64, i64);
539
540impl_promotes!(i8, u8 => i16, i32);
541impl_promotes!(i8, u16 => i32, i32);
542impl_promotes!(i8, u32 => i64, i64);
543impl_promotes!(i8, u64 => i64, i64);
544
545impl_promotes!(i16, u8 => i32, i32);
546impl_promotes!(i16, u16 => i32, i32);
547impl_promotes!(i16, u32 => i64, i64);
548impl_promotes!(i16, u64 => i64, i64);
549
550impl_promotes!(i32, u8 => i64, i64);
551impl_promotes!(i32, u16 => i64, i64);
552impl_promotes!(i32, u32 => i64, i64);
553impl_promotes!(i32, u64 => i64, i64);
554
555impl_promotes!(i64, u8 => i64, i64);
556impl_promotes!(i64, u16 => i64, i64);
557impl_promotes!(i64, u32 => i64, i64);
558impl_promotes!(i64, u64 => i64, i64);
559
560// Operators supporting mixed-resolution addition and subtraction (returning LHS resolution)
561impl<T, const FRAC: usize, U, const FRAC_R: usize> core::ops::Add<Fixed<U, FRAC_R>>
562    for Fixed<T, FRAC>
563where
564    T: PromoteAddSub<U> + Copy,
565    U: Copy,
566    T::Output: num_traits::Bounded + num_traits::NumCast + Copy,
567    Value<T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
568    Value<U, FRAC_R>: crate::fixed_format::ConvertValue<i128, FRAC>,
569{
570    type Output = Fixed<T::Output, FRAC>;
571
572    fn add(self, rhs: Fixed<U, FRAC_R>) -> Self::Output {
573        let lhs_promoted: Value<i128, FRAC> = self.value().convert_value();
574        let rhs_promoted: Value<i128, FRAC> = rhs.value().convert_value();
575        let sum: i128 =
576            crate::saturating_arithmetic::saturate_add(lhs_promoted.value, rhs_promoted.value);
577        Fixed::from_raw(crate::utility::clamp_cast(sum))
578    }
579}
580
581impl<T, const FRAC: usize, U, const FRAC_R: usize> core::ops::Sub<Fixed<U, FRAC_R>>
582    for Fixed<T, FRAC>
583where
584    T: PromoteAddSub<U> + Copy,
585    U: Copy,
586    T::Output: num_traits::Bounded + num_traits::NumCast + Copy,
587    Value<T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
588    Value<U, FRAC_R>: crate::fixed_format::ConvertValue<i128, FRAC>,
589{
590    type Output = Fixed<T::Output, FRAC>;
591
592    fn sub(self, rhs: Fixed<U, FRAC_R>) -> Self::Output {
593        let lhs_promoted: Value<i128, FRAC> = self.value().convert_value();
594        let rhs_promoted: Value<i128, FRAC> = rhs.value().convert_value();
595        let diff: i128 =
596            crate::saturating_arithmetic::saturate_subtract(lhs_promoted.value, rhs_promoted.value);
597        Fixed::from_raw(crate::utility::clamp_cast(diff))
598    }
599}
600
601impl<T, const FRAC: usize, U, const FRAC_R: usize> core::ops::Mul<Fixed<U, FRAC_R>>
602    for Fixed<T, FRAC>
603where
604    T: PromoteMul<U> + Into<i128> + Copy,
605    U: Into<i128> + Copy,
606    T::Output: num_traits::Bounded + num_traits::NumCast + Copy,
607{
608    type Output = Fixed<T::Output, FRAC>;
609
610    fn mul(self, rhs: Fixed<U, FRAC_R>) -> Self::Output {
611        self.mul_to::<U, FRAC_R, T::Output, FRAC>(rhs)
612    }
613}
614
615impl<I, const FRAC: usize> core::ops::Neg for Fixed<I, FRAC>
616where
617    I: num_traits::Bounded + num_traits::NumCast + Copy + num_traits::Signed + Into<i128>,
618{
619    type Output = Self;
620
621    fn neg(self) -> Self::Output {
622        let zero = 0i128;
623        let negated = crate::saturating_arithmetic::saturate_subtract(zero, self.value);
624        Self::from_raw(negated)
625    }
626}
627
628// Commutative operators between Fixed and raw integers
629macro_rules! impl_fixed_integer_arithmetic {
630    ($T:ty, $Int:ty) => {
631        impl<const FRAC: usize> core::ops::Add<$Int> for Fixed<$T, FRAC>
632        where
633            $T: PromoteAddSub<$Int> + Copy,
634            $Int: Copy,
635            <$T as PromoteAddSub<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
636            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
637            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
638        {
639            type Output = Fixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
640
641            fn add(self, rhs: $Int) -> Self::Output {
642                let lhs_promoted: Value<i128, FRAC> = self.value().convert_value();
643                let rhs_promoted: Value<i128, FRAC> =
644                    Value::<$Int, 0> { value: rhs }.convert_value();
645                let sum: i128 = crate::saturating_arithmetic::saturate_add(
646                    lhs_promoted.value,
647                    rhs_promoted.value,
648                );
649                Fixed::from_raw(crate::utility::clamp_cast(sum))
650            }
651        }
652
653        impl<const FRAC: usize> core::ops::Add<Fixed<$T, FRAC>> for $Int
654        where
655            $T: PromoteAddSub<$Int> + Copy,
656            $Int: Copy,
657            <$T as PromoteAddSub<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
658            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
659            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
660        {
661            type Output = Fixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
662
663            fn add(self, rhs: Fixed<$T, FRAC>) -> Self::Output {
664                let lhs_promoted: Value<i128, FRAC> =
665                    Value::<$Int, 0> { value: self }.convert_value();
666                let rhs_promoted: Value<i128, FRAC> = rhs.value().convert_value();
667                let sum: i128 = crate::saturating_arithmetic::saturate_add(
668                    lhs_promoted.value,
669                    rhs_promoted.value,
670                );
671                Fixed::from_raw(crate::utility::clamp_cast(sum))
672            }
673        }
674
675        impl<const FRAC: usize> core::ops::Sub<$Int> for Fixed<$T, FRAC>
676        where
677            $T: PromoteAddSub<$Int> + Copy,
678            $Int: Copy,
679            <$T as PromoteAddSub<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
680            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
681            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
682        {
683            type Output = Fixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
684
685            fn sub(self, rhs: $Int) -> Self::Output {
686                let lhs_promoted: Value<i128, FRAC> = self.value().convert_value();
687                let rhs_promoted: Value<i128, FRAC> =
688                    Value::<$Int, 0> { value: rhs }.convert_value();
689                let diff: i128 = crate::saturating_arithmetic::saturate_subtract(
690                    lhs_promoted.value,
691                    rhs_promoted.value,
692                );
693                Fixed::from_raw(crate::utility::clamp_cast(diff))
694            }
695        }
696
697        impl<const FRAC: usize> core::ops::Sub<Fixed<$T, FRAC>> for $Int
698        where
699            $T: PromoteAddSub<$Int> + Copy,
700            $Int: Copy,
701            <$T as PromoteAddSub<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
702            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
703            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
704        {
705            type Output = Fixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
706
707            fn sub(self, rhs: Fixed<$T, FRAC>) -> Self::Output {
708                let lhs_promoted: Value<i128, FRAC> =
709                    Value::<$Int, 0> { value: self }.convert_value();
710                let rhs_promoted: Value<i128, FRAC> = rhs.value().convert_value();
711                let diff: i128 = crate::saturating_arithmetic::saturate_subtract(
712                    lhs_promoted.value,
713                    rhs_promoted.value,
714                );
715                Fixed::from_raw(crate::utility::clamp_cast(diff))
716            }
717        }
718
719        impl<const FRAC: usize> core::ops::Mul<$Int> for Fixed<$T, FRAC>
720        where
721            $T: PromoteMul<$Int> + Copy,
722            $Int: Copy,
723            <$T as PromoteMul<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
724            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
725        {
726            type Output = Fixed<<$T as PromoteMul<$Int>>::Output, FRAC>;
727
728            fn mul(self, rhs: $Int) -> Self::Output {
729                let lhs_promoted: Value<i128, FRAC> = self.value().convert_value();
730                let rhs_i128: i128 = rhs.into();
731                let prod: i128 =
732                    crate::saturating_arithmetic::saturate_multiply(lhs_promoted.value, rhs_i128);
733                Fixed::from_raw(crate::utility::clamp_cast(prod))
734            }
735        }
736
737        impl<const FRAC: usize> core::ops::Mul<Fixed<$T, FRAC>> for $Int
738        where
739            $T: PromoteMul<$Int> + Copy,
740            $Int: Copy,
741            <$T as PromoteMul<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
742            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
743        {
744            type Output = Fixed<<$T as PromoteMul<$Int>>::Output, FRAC>;
745
746            fn mul(self, rhs: Fixed<$T, FRAC>) -> Self::Output {
747                rhs.mul(self)
748            }
749        }
750
751        impl<const FRAC: usize> core::ops::AddAssign<$Int> for Fixed<$T, FRAC>
752        where
753            $T: PromoteAddSub<$Int> + num_traits::Bounded + num_traits::NumCast + Copy,
754            $Int: Copy,
755            <$T as PromoteAddSub<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
756            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
757            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
758        {
759            fn add_assign(&mut self, rhs: $Int) {
760                let sum = core::ops::Add::add(*self, rhs);
761                self.value = crate::utility::clamp_cast(sum.raw_value());
762            }
763        }
764
765        impl<const FRAC: usize> core::ops::SubAssign<$Int> for Fixed<$T, FRAC>
766        where
767            $T: PromoteAddSub<$Int> + num_traits::Bounded + num_traits::NumCast + Copy,
768            $Int: Copy,
769            <$T as PromoteAddSub<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
770            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
771            Value<$Int, 0>: crate::fixed_format::ConvertValue<i128, FRAC>,
772        {
773            fn sub_assign(&mut self, rhs: $Int) {
774                let diff = core::ops::Sub::sub(*self, rhs);
775                self.value = crate::utility::clamp_cast(diff.raw_value());
776            }
777        }
778
779        impl<const FRAC: usize> core::ops::MulAssign<$Int> for Fixed<$T, FRAC>
780        where
781            $T: PromoteMul<$Int> + num_traits::Bounded + num_traits::NumCast + Copy,
782            $Int: Copy,
783            <$T as PromoteMul<$Int>>::Output: num_traits::Bounded + num_traits::NumCast + Copy,
784            Value<$T, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
785        {
786            fn mul_assign(&mut self, rhs: $Int) {
787                let prod = core::ops::Mul::mul(*self, rhs);
788                self.value = crate::utility::clamp_cast(prod.raw_value());
789            }
790        }
791    };
792}
793
794macro_rules! gen_fixed_integer_arithmetic {
795    (signed: [$($T:ty),*], $Int_list:tt) => {
796        $(
797            gen_fixed_integer_arithmetic!(@one $T, $Int_list);
798        )*
799    };
800    (unsigned: [$($T:ty),*], $Int_list:tt) => {
801        $(
802            gen_fixed_integer_arithmetic!(@one $T, $Int_list);
803        )*
804    };
805    (mixed: [$($T:ty),*], $Int_list:tt) => {
806        $(
807            gen_fixed_integer_arithmetic!(@one $T, $Int_list);
808        )*
809    };
810    (@one $T:ty, [$($Int:ty),*]) => {
811        $(
812            impl_fixed_integer_arithmetic!($T, $Int);
813        )*
814    };
815}
816
817gen_fixed_integer_arithmetic!(signed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
818gen_fixed_integer_arithmetic!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
819gen_fixed_integer_arithmetic!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
820gen_fixed_integer_arithmetic!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);
821// Compound assignments (supporting mixed types and resolutions)
822impl<I, const FRAC: usize, U, const FRAC_R: usize> core::ops::AddAssign<Fixed<U, FRAC_R>>
823    for Fixed<I, FRAC>
824where
825    I: PromoteAddSub<U> + Copy + num_traits::Bounded + num_traits::NumCast + TryFrom<I::Output>,
826    U: Copy,
827    I::Output: num_traits::Bounded + num_traits::NumCast + Copy + PartialOrd + num_traits::Zero,
828    Value<I, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
829    Value<U, FRAC_R>: crate::fixed_format::ConvertValue<i128, FRAC>,
830{
831    fn add_assign(&mut self, rhs: Fixed<U, FRAC_R>) {
832        let sum = *self + rhs;
833        self.value = crate::utility::clamp_cast(sum.raw_value());
834    }
835}
836
837impl<I, const FRAC: usize, U, const FRAC_R: usize> core::ops::SubAssign<Fixed<U, FRAC_R>>
838    for Fixed<I, FRAC>
839where
840    I: PromoteAddSub<U> + Copy + num_traits::Bounded + num_traits::NumCast + TryFrom<I::Output>,
841    U: Copy,
842    I::Output: num_traits::Bounded + num_traits::NumCast + Copy + PartialOrd + num_traits::Zero,
843    Value<I, FRAC>: crate::fixed_format::ConvertValue<i128, FRAC>,
844    Value<U, FRAC_R>: crate::fixed_format::ConvertValue<i128, FRAC>,
845{
846    fn sub_assign(&mut self, rhs: Fixed<U, FRAC_R>) {
847        let diff = *self - rhs;
848        self.value = crate::utility::clamp_cast(diff.raw_value());
849    }
850}
851
852impl<I, const FRAC: usize> core::ops::MulAssign<Fixed<I, FRAC>> for Fixed<I, FRAC>
853where
854    I: num_traits::Bounded + num_traits::NumCast + Into<i128> + Copy,
855{
856    fn mul_assign(&mut self, rhs: Self) {
857        *self = self.mul_to(rhs);
858    }
859}
860
861impl<I, const FRAC: usize> core::ops::DivAssign<Fixed<I, FRAC>> for Fixed<I, FRAC>
862where
863    Self: Copy,
864    Fixed<I, FRAC>: From<crate::expression::DivExpression<Self, Self>>,
865{
866    fn div_assign(&mut self, rhs: Self) {
867        *self = Self::from(crate::expression::DivExpression { numerator: *self, denominator: rhs });
868    }
869}
870
871// Display, Debug, LowerHex implementations
872impl<I, const FRAC: usize> core::fmt::Display for Fixed<I, FRAC>
873where
874    I: Into<i128> + Copy,
875{
876    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
877        let s = crate::string::String::new(*self, crate::string::Mode::Dec, f.precision());
878        f.write_str(s.as_str())
879    }
880}
881
882impl<I, const FRAC: usize> core::fmt::Debug for Fixed<I, FRAC>
883where
884    I: Into<i128> + Copy + core::fmt::Debug,
885{
886    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
887        let s = crate::string::String::new(*self, crate::string::Mode::Dec, None);
888        write!(f, "Fixed(raw: {:?}, value: {})", self.raw_value(), s.as_str())
889    }
890}
891
892impl<I, const FRAC: usize> core::fmt::LowerHex for Fixed<I, FRAC>
893where
894    I: Into<i128> + Copy,
895{
896    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
897        let s = crate::string::String::new(*self, crate::string::Mode::Hex, Some(0));
898        f.write_str(s.as_str())
899    }
900}
901
902/// A helper struct that implements `Display` to format a fixed-point number
903/// as a rational value (e.g., `1+1/2`).
904pub struct RationalFormatter<'a, I, const FRAC: usize>(&'a Fixed<I, FRAC>);
905
906impl<I, const FRAC: usize> core::fmt::Display for RationalFormatter<'_, I, FRAC>
907where
908    I: Into<i128> + Copy,
909{
910    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
911        let s = crate::string::String::new(*self.0, crate::string::Mode::DecRational, Some(0));
912        f.write_str(s.as_str())
913    }
914}
915
916impl<I, const FRAC: usize> Fixed<I, FRAC> {
917    /// Returns a helper that displays the fixed-point number formatted as
918    /// a rational fraction (e.g., `1+1/2`).
919    pub fn rational(&self) -> RationalFormatter<'_, I, FRAC> {
920        RationalFormatter(self)
921    }
922}