Skip to main content

ffl/
expression.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::Fixed;
6
7/// An expression representing deferred division.
8///
9/// Evaluations are deferred to preserve precision, scaling only when converted
10/// to a target resolution or assigned to a `Fixed` variable.
11pub struct DivExpression<L, R> {
12    /// The numerator of the division expression.
13    pub numerator: L,
14    /// The denominator of the division expression.
15    pub denominator: R,
16}
17
18/// An expression representing deferred multiplication.
19///
20/// The exact product is kept until the expression is converted to a target
21/// resolution or assigned to a `FastFixed` variable, so the result format does
22/// not depend on operand order.
23#[derive(Clone, Copy)]
24pub struct MulExpression<L, R> {
25    /// The left-hand factor of the multiplication expression.
26    pub lhs: L,
27    /// The right-hand factor of the multiplication expression.
28    pub rhs: R,
29}
30
31// Division operator for Fixed / Fixed
32impl<IL, const FL: usize, IR, const FR: usize> core::ops::Div<Fixed<IR, FR>> for Fixed<IL, FL> {
33    type Output = DivExpression<Fixed<IL, FL>, Fixed<IR, FR>>;
34
35    fn div(self, rhs: Fixed<IR, FR>) -> Self::Output {
36        DivExpression { numerator: self, denominator: rhs }
37    }
38}
39
40// Division operator for Fixed / Integer (promoted to Fixed<Integer, 0>)
41macro_rules! impl_div_by_int {
42    ($I_R:ty) => {
43        impl<IL, const FL: usize> core::ops::Div<$I_R> for Fixed<IL, FL> {
44            type Output = DivExpression<Fixed<IL, FL>, Fixed<$I_R, 0>>;
45
46            fn div(self, rhs: $I_R) -> Self::Output {
47                DivExpression { numerator: self, denominator: Fixed::from_raw(rhs) }
48            }
49        }
50    };
51}
52
53impl_div_by_int!(i8);
54impl_div_by_int!(i16);
55impl_div_by_int!(i32);
56impl_div_by_int!(i64);
57impl_div_by_int!(u8);
58impl_div_by_int!(u16);
59impl_div_by_int!(u32);
60impl_div_by_int!(u64);
61
62// Conversion from DivExpression to Fixed (performs the actual scaled division in raw i128 space)
63impl<I, const TARGET_FRAC: usize, IL, const FL: usize, IR, const FR: usize>
64    From<DivExpression<Fixed<IL, FL>, Fixed<IR, FR>>> for Fixed<I, TARGET_FRAC>
65where
66    I: crate::num_traits::Bounded + TryFrom<i128> + Copy,
67    IL: Into<i128> + Copy,
68    IR: Into<i128> + Copy,
69{
70    fn from(expr: DivExpression<Fixed<IL, FL>, Fixed<IR, FR>>) -> Self {
71        let num_raw: i128 = expr.numerator.raw_value().into();
72        let denom_raw: i128 = expr.denominator.raw_value().into();
73
74        let src_frac = FL;
75        let dst_frac = TARGET_FRAC + FR;
76
77        let num_scaled = if src_frac > dst_frac {
78            let delta = src_frac - dst_frac;
79            let rounded = crate::utility::round_half_to_even::<i128>(num_raw, delta);
80            if let Some(power) = 1i128.checked_shl(delta as u32) { rounded / power } else { 0 }
81        } else if src_frac < dst_frac {
82            let delta = dst_frac - src_frac;
83            // Note on saturation: Even if saturating_shl_i128 saturates to i128::MAX/MIN,
84            // dividing by denom_raw (at most 64-bit i64) leaves a quotient >= 2^64, which
85            // clamp_cast correctly clamps to I::MAX/MIN (since I is at most 64 bits).
86            crate::utility::saturating_shl_i128(num_raw, delta)
87        } else {
88            num_raw
89        };
90
91        if denom_raw == 0 {
92            panic!("Division by zero in DivExpression");
93        }
94
95        let quotient = num_scaled.checked_div(denom_raw).unwrap_or(i128::MAX);
96
97        let val: I = crate::utility::clamp_cast(quotient);
98        Self::from_raw(val)
99    }
100}
101
102/// Helper to create a Division Expression from a numerator and denominator.
103pub fn from_ratio<IL: Copy, IR: Copy>(
104    numerator: IL,
105    denominator: IR,
106) -> DivExpression<Fixed<IL, 0>, Fixed<IR, 0>> {
107    DivExpression {
108        numerator: Fixed::from_raw(numerator),
109        denominator: Fixed::from_raw(denominator),
110    }
111}
112
113/// Helper to coerce a division expression to a specific target resolution.
114pub fn to_resolution<const TARGET_FRAC: usize, I, L, R>(
115    expr: DivExpression<L, R>,
116) -> Fixed<I, TARGET_FRAC>
117where
118    Fixed<I, TARGET_FRAC>: From<DivExpression<L, R>>,
119{
120    Fixed::from(expr)
121}