Skip to main content

ffl/
fast.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::expression::{DivExpression, MulExpression};
6use crate::fixed::{Fixed, PromoteAddSub, PromoteMul};
7use crate::num_traits::{
8    self, Bounded, CheckedDiv, SaturatingAdd, SaturatingMul, SaturatingSub, Zero,
9};
10use crate::utility::{clamp_cast, round_half_to_even};
11
12mod private {
13    pub trait Sealed {}
14    impl Sealed for i8 {}
15    impl Sealed for i16 {}
16    impl Sealed for i32 {}
17    impl Sealed for i64 {}
18    impl Sealed for u8 {}
19    impl Sealed for u16 {}
20    impl Sealed for u32 {}
21    impl Sealed for u64 {}
22}
23
24pub trait FastInt:
25    private::Sealed + Copy + num_traits::NumCast + num_traits::Bounded + PartialOrd + num_traits::Zero
26{
27    type Intermediate: num_traits::PrimInt
28        + num_traits::Bounded
29        + num_traits::SaturatingAdd
30        + num_traits::SaturatingSub
31        + num_traits::SaturatingMul
32        + num_traits::CheckedDiv
33        + num_traits::NumCast;
34}
35
36impl FastInt for i8 {
37    type Intermediate = i64;
38}
39impl FastInt for i16 {
40    type Intermediate = i64;
41}
42impl FastInt for i32 {
43    type Intermediate = i64;
44}
45impl FastInt for i64 {
46    type Intermediate = i64;
47}
48
49impl FastInt for u8 {
50    type Intermediate = u64;
51}
52impl FastInt for u16 {
53    type Intermediate = u64;
54}
55impl FastInt for u32 {
56    type Intermediate = u64;
57}
58impl FastInt for u64 {
59    type Intermediate = u64;
60}
61
62#[inline]
63pub(crate) fn saturating_shl<I>(val: I, bits: usize) -> I
64where
65    I: num_traits::PrimInt + num_traits::Bounded + num_traits::SaturatingMul,
66{
67    if bits == 0 || val == I::zero() {
68        return val;
69    }
70    let integer_bits = core::mem::size_of::<I>() * 8;
71    let limit = if I::min_value() < I::zero() { integer_bits - 1 } else { integer_bits };
72    if bits >= limit {
73        if I::min_value() < I::zero() {
74            // Signed
75            return if val > I::zero() { I::max_value() } else { I::min_value() };
76        } else {
77            // Unsigned
78            return I::max_value();
79        }
80    }
81    let shift_factor = I::one() << bits;
82    val.saturating_mul(&shift_factor)
83}
84
85#[inline]
86pub(crate) fn div_power_of_two<I>(val: I, bits: usize) -> I
87where
88    I: num_traits::PrimInt + num_traits::Bounded,
89{
90    let integer_bits = core::mem::size_of::<I>() * 8;
91    let limit = if I::min_value() < I::zero() { integer_bits - 1 } else { integer_bits };
92    if bits == integer_bits - 1 && val == I::min_value() && I::min_value() < I::zero() {
93        I::zero().saturating_sub(&I::one())
94    } else if bits >= limit {
95        I::zero()
96    } else {
97        val / (I::one() << bits)
98    }
99}
100
101/// A wrapper around Fixed that forces intermediate calculations to be performed
102/// in 64-bit space for maximum execution efficiency on 64-bit targets.
103///
104/// # Warning: Range Reduction at High Resolutions
105///
106/// Because intermediate calculations (such as multiplication and division) are performed
107/// within 64-bit integers (either signed `i64` or unsigned `u64` depending on the operands)
108/// before rescaling, `FastFixed` saturates significantly earlier than 128-bit `Fixed`.
109///
110/// For high-resolution formats (e.g., `FRAC >= 32`), even multiplying modest values
111/// like `1.0 * 1.0` can result in early saturation (or scaling down to smaller values)
112/// because the 64-bit intermediate product overflows. Use `FastFixed` primarily when
113/// fractional bit resolutions are small enough to accommodate your expected dynamic range
114/// within 64 bits.
115#[repr(transparent)]
116#[derive(Clone, Copy, Default, Hash)]
117#[allow(clippy::derived_hash_with_manual_eq)]
118pub struct FastFixed<I, const FRAC: usize>(pub Fixed<I, FRAC>);
119
120impl<I: PartialEq + Copy, const FRAC: usize> PartialEq for FastFixed<I, FRAC> {
121    #[inline]
122    fn eq(&self, other: &Self) -> bool {
123        self.0.raw_value() == other.0.raw_value()
124    }
125}
126
127impl<I: Eq + Copy, const FRAC: usize> Eq for FastFixed<I, FRAC> {}
128
129impl<I: PartialOrd + Copy, const FRAC: usize> PartialOrd for FastFixed<I, FRAC> {
130    #[inline]
131    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
132        self.0.raw_value().partial_cmp(&other.0.raw_value())
133    }
134}
135
136impl<I: Ord + Copy, const FRAC: usize> Ord for FastFixed<I, FRAC> {
137    #[inline]
138    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
139        self.0.raw_value().cmp(&other.0.raw_value())
140    }
141}
142
143impl<I, const FRAC: usize> core::fmt::Debug for FastFixed<I, FRAC>
144where
145    I: Into<i128> + Copy + core::fmt::Debug,
146{
147    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
148        let s = crate::string::String::new(self.0, crate::string::Mode::Dec, None);
149        write!(f, "FastFixed(raw: {:?}, value: {})", self.0.raw_value(), s)
150    }
151}
152
153impl<I: Copy, const FRAC: usize> FastFixed<I, FRAC> {
154    /// Creates a FastFixed value from a raw underlying integer value.
155    pub const fn from_raw(value: I) -> Self {
156        Self(Fixed::from_raw(value))
157    }
158
159    /// Returns the raw fixed-point value as the underlying integer type.
160    pub const fn raw_value(&self) -> I {
161        self.0.raw_value()
162    }
163
164    /// Returns the underlying Fixed value.
165    pub const fn into_fixed(self) -> Fixed<I, FRAC> {
166        self.0
167    }
168}
169
170impl<I: Bounded + num_traits::NumCast + Copy, const FRAC: usize> FastFixed<I, FRAC> {
171    /// Returns the minimum value of this fixed point format.
172    pub fn min() -> Self {
173        Self(Fixed::min())
174    }
175
176    /// Returns the maximum value of this fixed point format.
177    pub fn max() -> Self {
178        Self(Fixed::max())
179    }
180}
181
182impl<I: Copy, const FRAC: usize> From<Fixed<I, FRAC>> for FastFixed<I, FRAC> {
183    fn from(val: Fixed<I, FRAC>) -> Self {
184        Self(val)
185    }
186}
187
188impl<I: Copy, const FRAC: usize> From<FastFixed<I, FRAC>> for Fixed<I, FRAC> {
189    fn from(val: FastFixed<I, FRAC>) -> Self {
190        val.0
191    }
192}
193
194// Negation
195impl<I, const FRAC: usize> core::ops::Neg for FastFixed<I, FRAC>
196where
197    I: num_traits::Signed
198        + num_traits::Bounded
199        + num_traits::Zero
200        + PartialOrd
201        + num_traits::NumCast
202        + Copy
203        + FastInt,
204{
205    type Output = Self;
206
207    fn neg(self) -> Self::Output {
208        type Intermediate<I> = <I as FastInt>::Intermediate;
209        let val_64: Intermediate<I> = clamp_cast(self.0.raw_value());
210        let negated = Intermediate::<I>::zero().saturating_sub(&val_64);
211        Self(Fixed::from_raw(clamp_cast(negated)))
212    }
213}
214
215// Operators: Add
216impl<T, const FRAC: usize, U, const FRAC_R: usize> core::ops::Add<FastFixed<U, FRAC_R>>
217    for FastFixed<T, FRAC>
218where
219    T: PromoteAddSub<U> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
220    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
221    <T as PromoteAddSub<U>>::Output: FastInt,
222{
223    type Output = FastFixed<<T as PromoteAddSub<U>>::Output, FRAC>;
224
225    fn add(self, rhs: FastFixed<U, FRAC_R>) -> Self::Output {
226        type Out<T, U> = <T as PromoteAddSub<U>>::Output;
227        type I64<T, U> = <Out<T, U> as FastInt>::Intermediate;
228
229        let val_l: I64<T, U> = clamp_cast(self.0.raw_value());
230        let val_r: I64<T, U> = clamp_cast(rhs.0.raw_value());
231
232        // Perform alignment shift in 64-bit space
233        let sum = if FRAC > FRAC_R {
234            let delta = FRAC - FRAC_R;
235            let val_r_shifted = saturating_shl(val_r, delta);
236            val_l.saturating_add(&val_r_shifted)
237        } else if FRAC < FRAC_R {
238            let delta = FRAC_R - FRAC;
239            let val_l_shifted = saturating_shl(val_l, delta);
240            let sum_aligned = val_l_shifted.saturating_add(&val_r);
241            let rounded = round_half_to_even::<I64<T, U>>(sum_aligned, delta);
242            div_power_of_two(rounded, delta)
243        } else {
244            val_l.saturating_add(&val_r)
245        };
246
247        FastFixed(Fixed::from_raw(clamp_cast(sum)))
248    }
249}
250
251// Operators: Sub
252impl<T, const FRAC: usize, U, const FRAC_R: usize> core::ops::Sub<FastFixed<U, FRAC_R>>
253    for FastFixed<T, FRAC>
254where
255    T: PromoteAddSub<U> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
256    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
257    <T as PromoteAddSub<U>>::Output: FastInt,
258{
259    type Output = FastFixed<<T as PromoteAddSub<U>>::Output, FRAC>;
260
261    fn sub(self, rhs: FastFixed<U, FRAC_R>) -> Self::Output {
262        type Out<T, U> = <T as PromoteAddSub<U>>::Output;
263        type I64<T, U> = <Out<T, U> as FastInt>::Intermediate;
264
265        let val_l: I64<T, U> = clamp_cast(self.0.raw_value());
266        let val_r: I64<T, U> = clamp_cast(rhs.0.raw_value());
267
268        // Perform alignment shift in 64-bit space
269        let diff = if FRAC > FRAC_R {
270            let delta = FRAC - FRAC_R;
271            let val_r_shifted = saturating_shl(val_r, delta);
272            val_l.saturating_sub(&val_r_shifted)
273        } else if FRAC < FRAC_R {
274            let delta = FRAC_R - FRAC;
275            let val_l_shifted = saturating_shl(val_l, delta);
276            let diff_aligned = val_l_shifted.saturating_sub(&val_r);
277            let rounded = round_half_to_even::<I64<T, U>>(diff_aligned, delta);
278            div_power_of_two(rounded, delta)
279        } else {
280            val_l.saturating_sub(&val_r)
281        };
282
283        FastFixed(Fixed::from_raw(clamp_cast(diff)))
284    }
285}
286
287// Operators: Mul (returns a deferred MulExpression containing FastFixed)
288impl<T, const FL: usize, U, const FR: usize> core::ops::Mul<FastFixed<U, FR>> for FastFixed<T, FL> {
289    type Output = MulExpression<FastFixed<T, FL>, FastFixed<U, FR>>;
290
291    fn mul(self, rhs: FastFixed<U, FR>) -> Self::Output {
292        MulExpression { lhs: self, rhs }
293    }
294}
295
296/// A deferred `FastFixed` expression.
297///
298/// Expressions carry the exact result of an operation (within the 64-bit
299/// intermediate type) together with its number of fractional bits, and are
300/// rounded exactly once when they are converted into a concrete `FastFixed`
301/// format with `From`/`Into` or [`FastExpression::evaluate`], mirroring the
302/// deferred expression templates of the C++ library. Expressions compose:
303/// adding or subtracting two expressions yields a deferred [`SumExpression`],
304/// while combining or comparing an expression with a `FastFixed` evaluates
305/// the expression in that `FastFixed`'s format.
306pub trait FastExpression: Copy {
307    /// The 64-bit intermediate integer type the expression is evaluated in.
308    type Int: num_traits::PrimInt
309        + Bounded
310        + SaturatingAdd
311        + SaturatingSub
312        + SaturatingMul
313        + num_traits::NumCast
314        + crate::num_traits::FixedInt;
315
316    /// The number of fractional bits of the raw value produced by `evaluate_raw`.
317    const FRAC: usize;
318
319    /// Evaluates the expression to its raw value with `Self::FRAC` fractional bits.
320    fn evaluate_raw(self) -> Self::Int;
321
322    /// Evaluates the expression into the given `FastFixed` format, rounding half
323    /// to even exactly once and saturating when the value does not fit.
324    fn evaluate<I, const FRAC_OUT: usize>(self) -> FastFixed<I, FRAC_OUT>
325    where
326        I: Bounded + num_traits::NumCast + Copy,
327    {
328        FastFixed(Fixed::from_raw(clamp_cast(rescale(self.evaluate_raw(), Self::FRAC, FRAC_OUT))))
329    }
330}
331
332/// Rescales a raw value from `src_frac` to `dst_frac` fractional bits, rounding
333/// half to even when precision is reduced and saturating when it is increased.
334#[inline]
335fn rescale<I>(value: I, src_frac: usize, dst_frac: usize) -> I
336where
337    I: num_traits::PrimInt + Bounded + SaturatingMul + crate::num_traits::FixedInt,
338{
339    if src_frac > dst_frac {
340        let delta = src_frac - dst_frac;
341        div_power_of_two(round_half_to_even::<I>(value, delta), delta)
342    } else if src_frac < dst_frac {
343        saturating_shl(value, dst_frac - src_frac)
344    } else {
345        value
346    }
347}
348
349impl<T, const FL: usize, U, const FR: usize> FastExpression
350    for MulExpression<FastFixed<T, FL>, FastFixed<U, FR>>
351where
352    T: PromoteMul<U> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
353    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
354    <T as PromoteMul<U>>::Output: FastInt,
355{
356    type Int = <<T as PromoteMul<U>>::Output as FastInt>::Intermediate;
357    const FRAC: usize = FL + FR;
358
359    fn evaluate_raw(self) -> Self::Int {
360        let val_l: Self::Int = clamp_cast(self.lhs.0.raw_value());
361        let val_r: Self::Int = clamp_cast(self.rhs.0.raw_value());
362        val_l.saturating_mul(&val_r)
363    }
364}
365
366/// A deferred sum or difference of two `FastFixed` expressions.
367///
368/// The operands are aligned to the lesser of their precisions before the
369/// addition, as in the C++ library.
370#[derive(Clone, Copy)]
371pub struct SumExpression<L, R> {
372    lhs: L,
373    rhs: R,
374    subtract: bool,
375}
376
377impl<L, R> FastExpression for SumExpression<L, R>
378where
379    L: FastExpression,
380    R: FastExpression<Int = L::Int>,
381{
382    type Int = L::Int;
383    const FRAC: usize = if L::FRAC < R::FRAC { L::FRAC } else { R::FRAC };
384
385    fn evaluate_raw(self) -> Self::Int {
386        let lhs = rescale(self.lhs.evaluate_raw(), L::FRAC, Self::FRAC);
387        let rhs = rescale(self.rhs.evaluate_raw(), R::FRAC, Self::FRAC);
388        if self.subtract { lhs.saturating_sub(&rhs) } else { lhs.saturating_add(&rhs) }
389    }
390}
391
392// Evaluation of expressions into a FastFixed format.
393impl<I, const FRAC_OUT: usize, L, R> From<MulExpression<L, R>> for FastFixed<I, FRAC_OUT>
394where
395    I: Bounded + num_traits::NumCast + Copy,
396    MulExpression<L, R>: FastExpression,
397{
398    fn from(expr: MulExpression<L, R>) -> Self {
399        expr.evaluate()
400    }
401}
402
403impl<I, const FRAC_OUT: usize, L, R> From<SumExpression<L, R>> for FastFixed<I, FRAC_OUT>
404where
405    I: Bounded + num_traits::NumCast + Copy,
406    SumExpression<L, R>: FastExpression,
407{
408    fn from(expr: SumExpression<L, R>) -> Self {
409        expr.evaluate()
410    }
411}
412
413// Comparison of a FastFixed with an expression: the expression is evaluated in
414// the format of the FastFixed operand, as in the C++ library.
415impl<I, const FRAC: usize, E> PartialEq<E> for FastFixed<I, FRAC>
416where
417    I: Bounded + num_traits::NumCast + PartialEq + Copy,
418    E: FastExpression,
419{
420    fn eq(&self, other: &E) -> bool {
421        *self == other.evaluate::<I, FRAC>()
422    }
423}
424
425impl<I, const FRAC: usize, E> PartialOrd<E> for FastFixed<I, FRAC>
426where
427    I: Bounded + num_traits::NumCast + PartialOrd + Copy,
428    E: FastExpression,
429{
430    fn partial_cmp(&self, other: &E) -> Option<core::cmp::Ordering> {
431        self.partial_cmp(&other.evaluate::<I, FRAC>())
432    }
433}
434
435/// Addition and subtraction involving expressions. A FastFixed operand fixes the
436/// result format: the expression is evaluated in that format first, which matches
437/// the C++ promotion to the lesser precision whenever the FastFixed operand is at
438/// most as precise as the expression. Two expressions combine into a deferred
439/// SumExpression.
440macro_rules! impl_fast_fixed_expression_add_sub {
441    ($Trait:ident, $method:ident, $op:tt) => {
442        impl<I, const FRAC: usize, E> core::ops::$Trait<E> for FastFixed<I, FRAC>
443        where
444            I: Bounded + num_traits::NumCast + Copy,
445            E: FastExpression,
446            FastFixed<I, FRAC>: core::ops::$Trait<FastFixed<I, FRAC>>,
447        {
448            type Output = <FastFixed<I, FRAC> as core::ops::$Trait<FastFixed<I, FRAC>>>::Output;
449
450            fn $method(self, rhs: E) -> Self::Output {
451                self $op rhs.evaluate::<I, FRAC>()
452            }
453        }
454    };
455}
456
457impl_fast_fixed_expression_add_sub!(Add, add, +);
458impl_fast_fixed_expression_add_sub!(Sub, sub, -);
459
460/// Expression-side operators. These are implemented per expression type rather
461/// than as a blanket impl over `FastExpression` because the orphan rules forbid
462/// implementing a foreign trait for a bare type parameter.
463macro_rules! impl_fast_expression_ops {
464    ($Expr:ident) => {
465        impl<I, const FRAC: usize, L, R> PartialEq<FastFixed<I, FRAC>> for $Expr<L, R>
466        where
467            I: Bounded + num_traits::NumCast + PartialEq + Copy,
468            $Expr<L, R>: FastExpression,
469        {
470            fn eq(&self, other: &FastFixed<I, FRAC>) -> bool {
471                self.evaluate::<I, FRAC>() == *other
472            }
473        }
474
475        impl<I, const FRAC: usize, L, R> PartialOrd<FastFixed<I, FRAC>> for $Expr<L, R>
476        where
477            I: Bounded + num_traits::NumCast + PartialOrd + Copy,
478            $Expr<L, R>: FastExpression,
479        {
480            fn partial_cmp(&self, other: &FastFixed<I, FRAC>) -> Option<core::cmp::Ordering> {
481                self.evaluate::<I, FRAC>().partial_cmp(other)
482            }
483        }
484
485        impl_fast_expression_ops!(@add_sub $Expr, Add, add, +, false);
486        impl_fast_expression_ops!(@add_sub $Expr, Sub, sub, -, true);
487    };
488    (@add_sub $Expr:ident, $Trait:ident, $method:ident, $op:tt, $subtract:expr) => {
489        impl<I, const FRAC: usize, L, R> core::ops::$Trait<FastFixed<I, FRAC>> for $Expr<L, R>
490        where
491            I: Bounded + num_traits::NumCast + Copy,
492            $Expr<L, R>: FastExpression,
493            FastFixed<I, FRAC>: core::ops::$Trait<FastFixed<I, FRAC>>,
494        {
495            type Output = <FastFixed<I, FRAC> as core::ops::$Trait<FastFixed<I, FRAC>>>::Output;
496
497            fn $method(self, rhs: FastFixed<I, FRAC>) -> Self::Output {
498                self.evaluate::<I, FRAC>() $op rhs
499            }
500        }
501
502        impl<L, R, E> core::ops::$Trait<E> for $Expr<L, R>
503        where
504            $Expr<L, R>: FastExpression,
505            E: FastExpression,
506        {
507            type Output = SumExpression<$Expr<L, R>, E>;
508
509            fn $method(self, rhs: E) -> Self::Output {
510                SumExpression { lhs: self, rhs, subtract: $subtract }
511            }
512        }
513    };
514}
515
516impl_fast_expression_ops!(MulExpression);
517impl_fast_expression_ops!(SumExpression);
518
519// Format conversion.
520impl<R, const FRAC_OUT: usize> FastFixed<R, FRAC_OUT>
521where
522    R: Bounded + num_traits::NumCast + Copy,
523{
524    /// Converts `other` into this format, rounding half to even when precision
525    /// is reduced and saturating when the value does not fit.
526    ///
527    /// This is the equivalent of the C++ `ffl` idiom `Target{other}` and replaces
528    /// the `Fixed::from_fixed(other.into_fixed()).into()` round trip.
529    pub fn from_fixed<OtherI: Copy, const OTHER_FRAC: usize>(
530        other: FastFixed<OtherI, OTHER_FRAC>,
531    ) -> Self
532    where
533        crate::fixed_format::Value<OtherI, OTHER_FRAC>:
534            crate::fixed_format::ConvertValue<R, FRAC_OUT>,
535    {
536        FastFixed(Fixed::from_fixed(other.0))
537    }
538}
539
540// Operators: Div (returns a DivExpression containing FastFixed)
541impl<IL, const FL: usize, IR, const FR: usize> core::ops::Div<FastFixed<IR, FR>>
542    for FastFixed<IL, FL>
543{
544    type Output = DivExpression<FastFixed<IL, FL>, FastFixed<IR, FR>>;
545
546    fn div(self, rhs: FastFixed<IR, FR>) -> Self::Output {
547        DivExpression { numerator: self, denominator: rhs }
548    }
549}
550
551impl<IL, const FL: usize, IR, const FR: usize> DivExpression<FastFixed<IL, FL>, FastFixed<IR, FR>> {
552    /// Evaluates the deferred division into a FastFixed value of the target format.
553    ///
554    /// # Panics
555    ///
556    /// Panics if the denominator is zero.
557    pub fn evaluate<I, const TARGET_FRAC: usize>(self) -> FastFixed<I, TARGET_FRAC>
558    where
559        FastFixed<I, TARGET_FRAC>: From<Self>,
560    {
561        FastFixed::from(self)
562    }
563}
564
565// Division assignment evaluation
566impl<I, const TARGET_FRAC: usize, IL, const FL: usize, IR, const FR: usize>
567    From<DivExpression<FastFixed<IL, FL>, FastFixed<IR, FR>>> for FastFixed<I, TARGET_FRAC>
568where
569    I: num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
570    IL: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
571    IR: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
572{
573    fn from(expr: DivExpression<FastFixed<IL, FL>, FastFixed<IR, FR>>) -> Self {
574        type I64<I> = <I as FastInt>::Intermediate;
575        let num_raw: I64<I> = clamp_cast(expr.numerator.0.raw_value());
576        let denom_raw: I64<I> = clamp_cast(expr.denominator.0.raw_value());
577
578        let dst_frac = TARGET_FRAC + FR;
579
580        let num_scaled = if FL > dst_frac {
581            let delta = FL - dst_frac;
582            let rounded = round_half_to_even::<I64<I>>(num_raw, delta);
583            div_power_of_two(rounded, delta)
584        } else if FL < dst_frac {
585            let delta = dst_frac - FL;
586            saturating_shl(num_raw, delta)
587        } else {
588            num_raw
589        };
590
591        if denom_raw == I64::<I>::zero() {
592            panic!("Division by zero in DivExpression");
593        }
594        let quotient = if let Some(q) = num_scaled.checked_div(&denom_raw) {
595            q
596        } else {
597            I64::<I>::max_value()
598        };
599        FastFixed(Fixed::from_raw(clamp_cast(quotient)))
600    }
601}
602
603// Compound assignments
604impl<I, const FRAC: usize, U, const FRAC_R: usize> core::ops::AddAssign<FastFixed<U, FRAC_R>>
605    for FastFixed<I, FRAC>
606where
607    I: PromoteAddSub<U>
608        + num_traits::Zero
609        + PartialOrd
610        + num_traits::Bounded
611        + num_traits::NumCast
612        + Copy
613        + FastInt
614        + TryFrom<<I as PromoteAddSub<U>>::Output>,
615    <I as PromoteAddSub<U>>::Output: FastInt,
616    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
617    FastFixed<I, FRAC>: core::ops::Add<
618            FastFixed<U, FRAC_R>,
619            Output = FastFixed<<I as PromoteAddSub<U>>::Output, FRAC>,
620        >,
621{
622    fn add_assign(&mut self, rhs: FastFixed<U, FRAC_R>) {
623        let sum = *self + rhs;
624        self.0 = Fixed::from_raw(clamp_cast(sum.0.raw_value()));
625    }
626}
627
628impl<I, const FRAC: usize, U, const FRAC_R: usize> core::ops::SubAssign<FastFixed<U, FRAC_R>>
629    for FastFixed<I, FRAC>
630where
631    I: PromoteAddSub<U>
632        + num_traits::Zero
633        + PartialOrd
634        + num_traits::Bounded
635        + num_traits::NumCast
636        + Copy
637        + FastInt
638        + TryFrom<<I as PromoteAddSub<U>>::Output>,
639    <I as PromoteAddSub<U>>::Output: FastInt,
640    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
641    FastFixed<I, FRAC>: core::ops::Sub<
642            FastFixed<U, FRAC_R>,
643            Output = FastFixed<<I as PromoteAddSub<U>>::Output, FRAC>,
644        >,
645{
646    fn sub_assign(&mut self, rhs: FastFixed<U, FRAC_R>) {
647        let diff = *self - rhs;
648        self.0 = Fixed::from_raw(clamp_cast(diff.0.raw_value()));
649    }
650}
651
652impl<I, const FRAC: usize> core::ops::MulAssign<FastFixed<I, FRAC>> for FastFixed<I, FRAC>
653where
654    I: PromoteMul<I>
655        + num_traits::Zero
656        + PartialOrd
657        + num_traits::Bounded
658        + num_traits::NumCast
659        + Copy
660        + FastInt
661        + TryFrom<<I as PromoteMul<I>>::Output>,
662    <I as PromoteMul<I>>::Output: FastInt,
663    FastFixed<I, FRAC>:
664        core::ops::Mul<FastFixed<I, FRAC>, Output = FastFixed<<I as PromoteMul<I>>::Output, FRAC>>,
665{
666    fn mul_assign(&mut self, rhs: Self) {
667        let prod = *self * rhs;
668        self.0 = Fixed::from_raw(clamp_cast(prod.0.raw_value()));
669    }
670}
671
672impl<I, const FRAC: usize> core::ops::DivAssign<FastFixed<I, FRAC>> for FastFixed<I, FRAC>
673where
674    Self: Copy,
675    I: num_traits::Zero + PartialOrd,
676    FastFixed<I, FRAC>: From<DivExpression<Self, Self>>,
677{
678    fn div_assign(&mut self, rhs: Self) {
679        *self = Self::from(DivExpression { numerator: *self, denominator: rhs });
680    }
681}
682
683// Display, LowerHex implementations
684impl<I, const FRAC: usize> core::fmt::Display for FastFixed<I, FRAC>
685where
686    I: Into<i128> + Copy,
687{
688    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
689        let s = crate::string::String::new(self.0, crate::string::Mode::Dec, f.precision());
690        write!(f, "{}", s)
691    }
692}
693
694impl<I, const FRAC: usize> core::fmt::LowerHex for FastFixed<I, FRAC>
695where
696    I: Into<i128> + Copy,
697{
698    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
699        let s = crate::string::String::new(self.0, crate::string::Mode::Hex, Some(0));
700        write!(f, "{}", s)
701    }
702}
703
704// Commutative relational operators between FastFixed and raw integers
705macro_rules! impl_fast_fixed_integer_comparison {
706    ($T:ty, $Int:ty) => {
707        impl<const FRAC: usize> PartialEq<$Int> for FastFixed<$T, FRAC>
708        where
709            Fixed<$T, FRAC>: PartialEq<$Int>,
710        {
711            fn eq(&self, other: &$Int) -> bool {
712                self.0 == *other
713            }
714        }
715
716        impl<const FRAC: usize> PartialEq<FastFixed<$T, FRAC>> for $Int
717        where
718            Fixed<$T, FRAC>: PartialEq<$Int>,
719        {
720            fn eq(&self, other: &FastFixed<$T, FRAC>) -> bool {
721                other.0 == *self
722            }
723        }
724
725        impl<const FRAC: usize> PartialOrd<$Int> for FastFixed<$T, FRAC>
726        where
727            Fixed<$T, FRAC>: PartialOrd<$Int>,
728        {
729            fn partial_cmp(&self, other: &$Int) -> Option<core::cmp::Ordering> {
730                self.0.partial_cmp(other)
731            }
732        }
733
734        impl<const FRAC: usize> PartialOrd<FastFixed<$T, FRAC>> for $Int
735        where
736            Fixed<$T, FRAC>: PartialOrd<$Int>,
737        {
738            fn partial_cmp(&self, other: &FastFixed<$T, FRAC>) -> Option<core::cmp::Ordering> {
739                other.0.partial_cmp(self).map(|o| o.reverse())
740            }
741        }
742    };
743}
744
745macro_rules! gen_fast_fixed_integer_comparisons {
746    ($label:ident: [$($T:ty),*], $Int_list:tt) => {
747        $(
748            gen_fast_fixed_integer_comparisons!(@one $T, $Int_list);
749        )*
750    };
751    (@one $T:ty, [$($Int:ty),*]) => {
752        $(
753            impl_fast_fixed_integer_comparison!($T, $Int);
754        )*
755    };
756}
757
758gen_fast_fixed_integer_comparisons!(signed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
759gen_fast_fixed_integer_comparisons!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
760gen_fast_fixed_integer_comparisons!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
761gen_fast_fixed_integer_comparisons!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);
762
763// Commutative operators between FastFixed and raw integers
764macro_rules! impl_fast_fixed_integer_arithmetic {
765    ($T:ty, $Int:ty) => {
766        impl<const FRAC: usize> core::ops::Add<$Int> for FastFixed<$T, FRAC>
767        where
768            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
769            $Int: num_traits::NumCast + Copy,
770            <$T as PromoteAddSub<$Int>>::Output:
771                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
772        {
773            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
774
775            fn add(self, rhs: $Int) -> Self::Output {
776                type Out = <$T as PromoteAddSub<$Int>>::Output;
777                type I64 = <Out as FastInt>::Intermediate;
778                let val_l: I64 = clamp_cast(self.0.raw_value());
779                let val_r: I64 = clamp_cast(rhs);
780                let val_r_shifted = saturating_shl(val_r, FRAC);
781                let sum = val_l.saturating_add(val_r_shifted);
782                FastFixed(Fixed::from_raw(clamp_cast(sum)))
783            }
784        }
785
786        impl<const FRAC: usize> core::ops::Add<FastFixed<$T, FRAC>> for $Int
787        where
788            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
789            $Int: num_traits::NumCast + Copy,
790            <$T as PromoteAddSub<$Int>>::Output:
791                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
792        {
793            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
794
795            fn add(self, rhs: FastFixed<$T, FRAC>) -> Self::Output {
796                type Out = <$T as PromoteAddSub<$Int>>::Output;
797                type I64 = <Out as FastInt>::Intermediate;
798                let val_l: I64 = clamp_cast(self);
799                let val_r: I64 = clamp_cast(rhs.0.raw_value());
800                let val_l_shifted = saturating_shl(val_l, FRAC);
801                let sum = val_l_shifted.saturating_add(val_r);
802                FastFixed(Fixed::from_raw(clamp_cast(sum)))
803            }
804        }
805
806        impl<const FRAC: usize> core::ops::Sub<$Int> for FastFixed<$T, FRAC>
807        where
808            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
809            $Int: num_traits::NumCast + Copy,
810            <$T as PromoteAddSub<$Int>>::Output:
811                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
812        {
813            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
814
815            fn sub(self, rhs: $Int) -> Self::Output {
816                type Out = <$T as PromoteAddSub<$Int>>::Output;
817                type I64 = <Out as FastInt>::Intermediate;
818                let val_l: I64 = clamp_cast(self.0.raw_value());
819                let val_r: I64 = clamp_cast(rhs);
820                let val_r_shifted = saturating_shl(val_r, FRAC);
821                let diff = val_l.saturating_sub(val_r_shifted);
822                FastFixed(Fixed::from_raw(clamp_cast(diff)))
823            }
824        }
825
826        impl<const FRAC: usize> core::ops::Sub<FastFixed<$T, FRAC>> for $Int
827        where
828            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
829            $Int: num_traits::NumCast + Copy,
830            <$T as PromoteAddSub<$Int>>::Output:
831                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
832        {
833            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
834
835            fn sub(self, rhs: FastFixed<$T, FRAC>) -> Self::Output {
836                type Out = <$T as PromoteAddSub<$Int>>::Output;
837                type I64 = <Out as FastInt>::Intermediate;
838                let val_l: I64 = clamp_cast(self);
839                let val_r: I64 = clamp_cast(rhs.0.raw_value());
840                let val_l_shifted = saturating_shl(val_l, FRAC);
841                let diff = val_l_shifted.saturating_sub(val_r);
842                FastFixed(Fixed::from_raw(clamp_cast(diff)))
843            }
844        }
845
846        impl<const FRAC: usize> core::ops::Mul<$Int> for FastFixed<$T, FRAC>
847        where
848            $T: PromoteMul<$Int> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
849            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
850            <$T as PromoteMul<$Int>>::Output:
851                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
852        {
853            type Output = FastFixed<<$T as PromoteMul<$Int>>::Output, FRAC>;
854
855            fn mul(self, rhs: $Int) -> Self::Output {
856                type Out = <$T as PromoteMul<$Int>>::Output;
857                type I64 = <Out as FastInt>::Intermediate;
858                let val_l: I64 = clamp_cast(self.0.raw_value());
859                let val_r: I64 = clamp_cast(rhs);
860                let prod = val_l.saturating_mul(val_r);
861                FastFixed(Fixed::from_raw(clamp_cast(prod)))
862            }
863        }
864
865        impl<const FRAC: usize> core::ops::Mul<FastFixed<$T, FRAC>> for $Int
866        where
867            $T: PromoteMul<$Int> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
868            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
869            <$T as PromoteMul<$Int>>::Output:
870                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
871        {
872            type Output = FastFixed<<$T as PromoteMul<$Int>>::Output, FRAC>;
873
874            fn mul(self, rhs: FastFixed<$T, FRAC>) -> Self::Output {
875                rhs.mul(self)
876            }
877        }
878
879        impl<const FRAC: usize> core::ops::AddAssign<$Int> for FastFixed<$T, FRAC>
880        where
881            $T: PromoteAddSub<$Int>
882                + num_traits::Zero
883                + PartialOrd
884                + num_traits::Bounded
885                + num_traits::NumCast
886                + Copy
887                + FastInt,
888            <$T as PromoteAddSub<$Int>>::Output: FastInt,
889            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
890            FastFixed<$T, FRAC>:
891                core::ops::Add<$Int, Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>>,
892        {
893            fn add_assign(&mut self, rhs: $Int) {
894                let sum = *self + rhs;
895                self.0 = Fixed::from_raw(clamp_cast(sum.0.raw_value()));
896            }
897        }
898
899        impl<const FRAC: usize> core::ops::SubAssign<$Int> for FastFixed<$T, FRAC>
900        where
901            $T: PromoteAddSub<$Int>
902                + num_traits::Zero
903                + PartialOrd
904                + num_traits::Bounded
905                + num_traits::NumCast
906                + Copy
907                + FastInt,
908            <$T as PromoteAddSub<$Int>>::Output: FastInt,
909            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
910            FastFixed<$T, FRAC>:
911                core::ops::Sub<$Int, Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>>,
912        {
913            fn sub_assign(&mut self, rhs: $Int) {
914                let diff = *self - rhs;
915                self.0 = Fixed::from_raw(clamp_cast(diff.0.raw_value()));
916            }
917        }
918
919        impl<const FRAC: usize> core::ops::MulAssign<$Int> for FastFixed<$T, FRAC>
920        where
921            $T: PromoteMul<$Int>
922                + num_traits::Zero
923                + PartialOrd
924                + num_traits::Bounded
925                + num_traits::NumCast
926                + Copy
927                + FastInt,
928            <$T as PromoteMul<$Int>>::Output: FastInt,
929            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
930            FastFixed<$T, FRAC>:
931                core::ops::Mul<$Int, Output = FastFixed<<$T as PromoteMul<$Int>>::Output, FRAC>>,
932        {
933            fn mul_assign(&mut self, rhs: $Int) {
934                let prod = *self * rhs;
935                self.0 = Fixed::from_raw(clamp_cast(prod.0.raw_value()));
936            }
937        }
938
939        impl<const FRAC: usize> core::ops::Div<$Int> for FastFixed<$T, FRAC> {
940            type Output = DivExpression<FastFixed<$T, FRAC>, FastFixed<$Int, 0>>;
941
942            fn div(self, rhs: $Int) -> Self::Output {
943                DivExpression { numerator: self, denominator: FastFixed(Fixed::from_raw(rhs)) }
944            }
945        }
946
947        impl<const FRAC: usize> core::ops::DivAssign<$Int> for FastFixed<$T, FRAC>
948        where
949            Self: Copy,
950            FastFixed<$T, FRAC>: From<DivExpression<FastFixed<$T, FRAC>, FastFixed<$Int, 0>>>,
951        {
952            fn div_assign(&mut self, rhs: $Int) {
953                *self = (*self / rhs).into();
954            }
955        }
956    };
957}
958
959macro_rules! gen_fast_fixed_integer_arithmetic {
960    ($label:ident: [$($T:ty),*], $Int_list:tt) => {
961        $(
962            gen_fast_fixed_integer_arithmetic!(@one $T, $Int_list);
963        )*
964    };
965    (@one $T:ty, [$($Int:ty),*]) => {
966        $(
967            impl_fast_fixed_integer_arithmetic!($T, $Int);
968        )*
969    };
970}
971
972gen_fast_fixed_integer_arithmetic!(signed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
973gen_fast_fixed_integer_arithmetic!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
974gen_fast_fixed_integer_arithmetic!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
975gen_fast_fixed_integer_arithmetic!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);