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
603impl<I, const TARGET_FRAC: usize, IL, const FL: usize, IR, const FR: usize>
604    From<DivExpression<Fixed<IL, FL>, Fixed<IR, FR>>> for FastFixed<I, TARGET_FRAC>
605where
606    FastFixed<I, TARGET_FRAC>: From<DivExpression<FastFixed<IL, FL>, FastFixed<IR, FR>>>,
607{
608    fn from(expr: DivExpression<Fixed<IL, FL>, Fixed<IR, FR>>) -> Self {
609        Self::from(DivExpression {
610            numerator: FastFixed(expr.numerator),
611            denominator: FastFixed(expr.denominator),
612        })
613    }
614}
615
616// Compound assignments
617impl<I, const FRAC: usize, U, const FRAC_R: usize> core::ops::AddAssign<FastFixed<U, FRAC_R>>
618    for FastFixed<I, FRAC>
619where
620    I: PromoteAddSub<U>
621        + num_traits::Zero
622        + PartialOrd
623        + num_traits::Bounded
624        + num_traits::NumCast
625        + Copy
626        + FastInt
627        + TryFrom<<I as PromoteAddSub<U>>::Output>,
628    <I as PromoteAddSub<U>>::Output: FastInt,
629    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
630    FastFixed<I, FRAC>: core::ops::Add<
631            FastFixed<U, FRAC_R>,
632            Output = FastFixed<<I as PromoteAddSub<U>>::Output, FRAC>,
633        >,
634{
635    fn add_assign(&mut self, rhs: FastFixed<U, FRAC_R>) {
636        let sum = *self + rhs;
637        self.0 = Fixed::from_raw(clamp_cast(sum.0.raw_value()));
638    }
639}
640
641impl<I, const FRAC: usize, U, const FRAC_R: usize> core::ops::SubAssign<FastFixed<U, FRAC_R>>
642    for FastFixed<I, FRAC>
643where
644    I: PromoteAddSub<U>
645        + num_traits::Zero
646        + PartialOrd
647        + num_traits::Bounded
648        + num_traits::NumCast
649        + Copy
650        + FastInt
651        + TryFrom<<I as PromoteAddSub<U>>::Output>,
652    <I as PromoteAddSub<U>>::Output: FastInt,
653    U: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
654    FastFixed<I, FRAC>: core::ops::Sub<
655            FastFixed<U, FRAC_R>,
656            Output = FastFixed<<I as PromoteAddSub<U>>::Output, FRAC>,
657        >,
658{
659    fn sub_assign(&mut self, rhs: FastFixed<U, FRAC_R>) {
660        let diff = *self - rhs;
661        self.0 = Fixed::from_raw(clamp_cast(diff.0.raw_value()));
662    }
663}
664
665impl<I, const FRAC: usize> core::ops::MulAssign<FastFixed<I, FRAC>> for FastFixed<I, FRAC>
666where
667    I: PromoteMul<I>
668        + num_traits::Zero
669        + PartialOrd
670        + num_traits::Bounded
671        + num_traits::NumCast
672        + Copy
673        + FastInt
674        + TryFrom<<I as PromoteMul<I>>::Output>,
675    <I as PromoteMul<I>>::Output: FastInt,
676    FastFixed<I, FRAC>:
677        core::ops::Mul<FastFixed<I, FRAC>, Output = FastFixed<<I as PromoteMul<I>>::Output, FRAC>>,
678{
679    fn mul_assign(&mut self, rhs: Self) {
680        let prod = *self * rhs;
681        self.0 = Fixed::from_raw(clamp_cast(prod.0.raw_value()));
682    }
683}
684
685impl<I, const FRAC: usize> core::ops::DivAssign<FastFixed<I, FRAC>> for FastFixed<I, FRAC>
686where
687    Self: Copy,
688    I: num_traits::Zero + PartialOrd,
689    FastFixed<I, FRAC>: From<DivExpression<Self, Self>>,
690{
691    fn div_assign(&mut self, rhs: Self) {
692        *self = Self::from(DivExpression { numerator: *self, denominator: rhs });
693    }
694}
695
696// Display, LowerHex implementations
697impl<I, const FRAC: usize> core::fmt::Display for FastFixed<I, FRAC>
698where
699    I: Into<i128> + Copy,
700{
701    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
702        let s = crate::string::String::new(self.0, crate::string::Mode::Dec, f.precision());
703        write!(f, "{}", s)
704    }
705}
706
707impl<I, const FRAC: usize> core::fmt::LowerHex for FastFixed<I, FRAC>
708where
709    I: Into<i128> + Copy,
710{
711    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
712        let s = crate::string::String::new(self.0, crate::string::Mode::Hex, Some(0));
713        write!(f, "{}", s)
714    }
715}
716
717// Commutative relational operators between FastFixed and raw integers
718macro_rules! impl_fast_fixed_integer_comparison {
719    ($T:ty, $Int:ty) => {
720        impl<const FRAC: usize> PartialEq<$Int> for FastFixed<$T, FRAC>
721        where
722            Fixed<$T, FRAC>: PartialEq<$Int>,
723        {
724            fn eq(&self, other: &$Int) -> bool {
725                self.0 == *other
726            }
727        }
728
729        impl<const FRAC: usize> PartialEq<FastFixed<$T, FRAC>> for $Int
730        where
731            Fixed<$T, FRAC>: PartialEq<$Int>,
732        {
733            fn eq(&self, other: &FastFixed<$T, FRAC>) -> bool {
734                other.0 == *self
735            }
736        }
737
738        impl<const FRAC: usize> PartialOrd<$Int> for FastFixed<$T, FRAC>
739        where
740            Fixed<$T, FRAC>: PartialOrd<$Int>,
741        {
742            fn partial_cmp(&self, other: &$Int) -> Option<core::cmp::Ordering> {
743                self.0.partial_cmp(other)
744            }
745        }
746
747        impl<const FRAC: usize> PartialOrd<FastFixed<$T, FRAC>> for $Int
748        where
749            Fixed<$T, FRAC>: PartialOrd<$Int>,
750        {
751            fn partial_cmp(&self, other: &FastFixed<$T, FRAC>) -> Option<core::cmp::Ordering> {
752                other.0.partial_cmp(self).map(|o| o.reverse())
753            }
754        }
755    };
756}
757
758macro_rules! gen_fast_fixed_integer_comparisons {
759    ($label:ident: [$($T:ty),*], $Int_list:tt) => {
760        $(
761            gen_fast_fixed_integer_comparisons!(@one $T, $Int_list);
762        )*
763    };
764    (@one $T:ty, [$($Int:ty),*]) => {
765        $(
766            impl_fast_fixed_integer_comparison!($T, $Int);
767        )*
768    };
769}
770
771gen_fast_fixed_integer_comparisons!(signed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
772gen_fast_fixed_integer_comparisons!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
773gen_fast_fixed_integer_comparisons!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
774gen_fast_fixed_integer_comparisons!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);
775
776// Commutative operators between FastFixed and raw integers
777macro_rules! impl_fast_fixed_integer_arithmetic {
778    ($T:ty, $Int:ty) => {
779        impl<const FRAC: usize> core::ops::Add<$Int> for FastFixed<$T, FRAC>
780        where
781            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
782            $Int: num_traits::NumCast + Copy,
783            <$T as PromoteAddSub<$Int>>::Output:
784                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
785        {
786            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
787
788            fn add(self, rhs: $Int) -> Self::Output {
789                type Out = <$T as PromoteAddSub<$Int>>::Output;
790                type I64 = <Out as FastInt>::Intermediate;
791                let val_l: I64 = clamp_cast(self.0.raw_value());
792                let val_r: I64 = clamp_cast(rhs);
793                let val_r_shifted = saturating_shl(val_r, FRAC);
794                let sum = val_l.saturating_add(val_r_shifted);
795                FastFixed(Fixed::from_raw(clamp_cast(sum)))
796            }
797        }
798
799        impl<const FRAC: usize> core::ops::Add<FastFixed<$T, FRAC>> for $Int
800        where
801            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
802            $Int: num_traits::NumCast + Copy,
803            <$T as PromoteAddSub<$Int>>::Output:
804                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
805        {
806            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
807
808            fn add(self, rhs: FastFixed<$T, FRAC>) -> Self::Output {
809                type Out = <$T as PromoteAddSub<$Int>>::Output;
810                type I64 = <Out as FastInt>::Intermediate;
811                let val_l: I64 = clamp_cast(self);
812                let val_r: I64 = clamp_cast(rhs.0.raw_value());
813                let val_l_shifted = saturating_shl(val_l, FRAC);
814                let sum = val_l_shifted.saturating_add(val_r);
815                FastFixed(Fixed::from_raw(clamp_cast(sum)))
816            }
817        }
818
819        impl<const FRAC: usize> core::ops::Sub<$Int> for FastFixed<$T, FRAC>
820        where
821            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
822            $Int: num_traits::NumCast + Copy,
823            <$T as PromoteAddSub<$Int>>::Output:
824                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
825        {
826            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
827
828            fn sub(self, rhs: $Int) -> Self::Output {
829                type Out = <$T as PromoteAddSub<$Int>>::Output;
830                type I64 = <Out as FastInt>::Intermediate;
831                let val_l: I64 = clamp_cast(self.0.raw_value());
832                let val_r: I64 = clamp_cast(rhs);
833                let val_r_shifted = saturating_shl(val_r, FRAC);
834                let diff = val_l.saturating_sub(val_r_shifted);
835                FastFixed(Fixed::from_raw(clamp_cast(diff)))
836            }
837        }
838
839        impl<const FRAC: usize> core::ops::Sub<FastFixed<$T, FRAC>> for $Int
840        where
841            $T: PromoteAddSub<$Int> + num_traits::NumCast + Copy,
842            $Int: num_traits::NumCast + Copy,
843            <$T as PromoteAddSub<$Int>>::Output:
844                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
845        {
846            type Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>;
847
848            fn sub(self, rhs: FastFixed<$T, FRAC>) -> Self::Output {
849                type Out = <$T as PromoteAddSub<$Int>>::Output;
850                type I64 = <Out as FastInt>::Intermediate;
851                let val_l: I64 = clamp_cast(self);
852                let val_r: I64 = clamp_cast(rhs.0.raw_value());
853                let val_l_shifted = saturating_shl(val_l, FRAC);
854                let diff = val_l_shifted.saturating_sub(val_r);
855                FastFixed(Fixed::from_raw(clamp_cast(diff)))
856            }
857        }
858
859        impl<const FRAC: usize> core::ops::Mul<$Int> for FastFixed<$T, FRAC>
860        where
861            $T: PromoteMul<$Int> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
862            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
863            <$T as PromoteMul<$Int>>::Output:
864                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
865        {
866            type Output = FastFixed<<$T as PromoteMul<$Int>>::Output, FRAC>;
867
868            fn mul(self, rhs: $Int) -> Self::Output {
869                type Out = <$T as PromoteMul<$Int>>::Output;
870                type I64 = <Out as FastInt>::Intermediate;
871                let val_l: I64 = clamp_cast(self.0.raw_value());
872                let val_r: I64 = clamp_cast(rhs);
873                let prod = val_l.saturating_mul(val_r);
874                FastFixed(Fixed::from_raw(clamp_cast(prod)))
875            }
876        }
877
878        impl<const FRAC: usize> core::ops::Mul<FastFixed<$T, FRAC>> for $Int
879        where
880            $T: PromoteMul<$Int> + num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
881            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
882            <$T as PromoteMul<$Int>>::Output:
883                num_traits::Bounded + num_traits::NumCast + Copy + FastInt,
884        {
885            type Output = FastFixed<<$T as PromoteMul<$Int>>::Output, FRAC>;
886
887            fn mul(self, rhs: FastFixed<$T, FRAC>) -> Self::Output {
888                rhs.mul(self)
889            }
890        }
891
892        impl<const FRAC: usize> core::ops::AddAssign<$Int> for FastFixed<$T, FRAC>
893        where
894            $T: PromoteAddSub<$Int>
895                + num_traits::Zero
896                + PartialOrd
897                + num_traits::Bounded
898                + num_traits::NumCast
899                + Copy
900                + FastInt,
901            <$T as PromoteAddSub<$Int>>::Output: FastInt,
902            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
903            FastFixed<$T, FRAC>:
904                core::ops::Add<$Int, Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>>,
905        {
906            fn add_assign(&mut self, rhs: $Int) {
907                let sum = *self + rhs;
908                self.0 = Fixed::from_raw(clamp_cast(sum.0.raw_value()));
909            }
910        }
911
912        impl<const FRAC: usize> core::ops::SubAssign<$Int> for FastFixed<$T, FRAC>
913        where
914            $T: PromoteAddSub<$Int>
915                + num_traits::Zero
916                + PartialOrd
917                + num_traits::Bounded
918                + num_traits::NumCast
919                + Copy
920                + FastInt,
921            <$T as PromoteAddSub<$Int>>::Output: FastInt,
922            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
923            FastFixed<$T, FRAC>:
924                core::ops::Sub<$Int, Output = FastFixed<<$T as PromoteAddSub<$Int>>::Output, FRAC>>,
925        {
926            fn sub_assign(&mut self, rhs: $Int) {
927                let diff = *self - rhs;
928                self.0 = Fixed::from_raw(clamp_cast(diff.0.raw_value()));
929            }
930        }
931
932        impl<const FRAC: usize> core::ops::MulAssign<$Int> for FastFixed<$T, FRAC>
933        where
934            $T: PromoteMul<$Int>
935                + num_traits::Zero
936                + PartialOrd
937                + num_traits::Bounded
938                + num_traits::NumCast
939                + Copy
940                + FastInt,
941            <$T as PromoteMul<$Int>>::Output: FastInt,
942            $Int: num_traits::Zero + PartialOrd + num_traits::NumCast + Copy,
943            FastFixed<$T, FRAC>:
944                core::ops::Mul<$Int, Output = FastFixed<<$T as PromoteMul<$Int>>::Output, FRAC>>,
945        {
946            fn mul_assign(&mut self, rhs: $Int) {
947                let prod = *self * rhs;
948                self.0 = Fixed::from_raw(clamp_cast(prod.0.raw_value()));
949            }
950        }
951
952        impl<const FRAC: usize> core::ops::Div<$Int> for FastFixed<$T, FRAC> {
953            type Output = DivExpression<FastFixed<$T, FRAC>, FastFixed<$Int, 0>>;
954
955            fn div(self, rhs: $Int) -> Self::Output {
956                DivExpression { numerator: self, denominator: FastFixed(Fixed::from_raw(rhs)) }
957            }
958        }
959
960        impl<const FRAC: usize> core::ops::DivAssign<$Int> for FastFixed<$T, FRAC>
961        where
962            Self: Copy,
963            FastFixed<$T, FRAC>: From<DivExpression<FastFixed<$T, FRAC>, FastFixed<$Int, 0>>>,
964        {
965            fn div_assign(&mut self, rhs: $Int) {
966                *self = (*self / rhs).into();
967            }
968        }
969    };
970}
971
972macro_rules! gen_fast_fixed_integer_arithmetic {
973    ($label:ident: [$($T:ty),*], $Int_list:tt) => {
974        $(
975            gen_fast_fixed_integer_arithmetic!(@one $T, $Int_list);
976        )*
977    };
978    (@one $T:ty, [$($Int:ty),*]) => {
979        $(
980            impl_fast_fixed_integer_arithmetic!($T, $Int);
981        )*
982    };
983}
984
985gen_fast_fixed_integer_arithmetic!(signed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
986gen_fast_fixed_integer_arithmetic!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
987gen_fast_fixed_integer_arithmetic!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
988gen_fast_fixed_integer_arithmetic!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);