Skip to main content

ffl/
fixed_format.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
5/// Helper compile-time constants and utilities for a fixed-point format.
6pub struct FixedFormat<I, const FRAC: usize>(core::marker::PhantomData<I>);
7
8/// A wrapper type representing an un-scaled raw value in a fixed-point format,
9/// used for intermediate conversion step calculations.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct Value<I, const FRAC: usize> {
12    /// The underlying raw value.
13    pub value: I,
14}
15
16macro_rules! impl_fixed_format {
17    ($I:ty, $is_signed:expr, $bits:expr) => {
18        impl<const FRAC: usize> FixedFormat<$I, FRAC> {
19            pub const BITS: usize = $bits;
20            pub const FRACTIONAL_BITS: usize = FRAC;
21            pub const IS_SIGNED: bool = $is_signed;
22            pub const IS_UNSIGNED: bool = !$is_signed;
23            const _ASSERT_STRICT_FORMAT: () = {
24                let bits = core::mem::size_of::<$I>() * 8;
25                if $is_signed {
26                    assert!(
27                        FRAC < bits,
28                        "The number of fractional bits must be less than the total bit-width for signed types."
29                    );
30                } else {
31                    assert!(
32                        FRAC <= bits,
33                        "The number of fractional bits must be less than or equal to the total bit-width for unsigned types."
34                    );
35                }
36            };
37            pub const INTEGRAL_BITS: usize = {
38                let _ = Self::_ASSERT_STRICT_FORMAT;
39                $bits - FRAC - (if $is_signed { 1 } else { 0 })
40            };
41            pub const POWER: usize =
42                if FRAC >= $bits { 0 } else { 1usize.wrapping_shl(FRAC as u32) };
43            pub const FRACTIONAL_MASK: $I = if FRAC >= $bits {
44                !0 as $I
45            } else {
46                (1 as $I).wrapping_shl(FRAC as u32).wrapping_sub(1)
47            };
48            pub const INTEGRAL_MASK: $I = !Self::FRACTIONAL_MASK;
49            pub const SIGN_BIT: $I =
50                if $is_signed { (1 as $I).wrapping_shl(($bits - 1) as u32) } else { 0 };
51            pub const BINARY_POINT: $I =
52                if FRAC > 0 { (1 as $I).wrapping_shl((FRAC - 1) as u32) } else { 0 };
53            pub const ONES_PLACE: $I = (1 as $I).wrapping_shl(FRAC as u32);
54            pub const APPROXIMATE_UNIT: bool = ($is_signed && FRAC == $bits - 1) || FRAC == $bits;
55            pub const ADJUSTED_FRACTIONAL_BITS: usize =
56                FRAC - (if Self::APPROXIMATE_UNIT { 1 } else { 0 });
57            pub const ADJUSTED_POWER: $I =
58                (1 as $I).wrapping_shl(Self::ADJUSTED_FRACTIONAL_BITS as u32);
59            pub const ADJUSTMENT_FACTOR: $I =
60                if Self::APPROXIMATE_UNIT { 2 as $I } else { 1 as $I };
61            pub const ADJUSTED_FRACTIONAL_MASK: $I =
62                (1 as $I).wrapping_shl(Self::ADJUSTED_FRACTIONAL_BITS as u32).wrapping_sub(1);
63            pub const ADJUSTED_INTEGRAL_MASK: $I = !Self::ADJUSTED_FRACTIONAL_MASK;
64            pub const MIN: $I = <$I>::MIN;
65            pub const MAX: $I = <$I>::MAX;
66            pub const INTEGRAL_MIN: $I =
67                if FRAC >= $bits { 0 } else { ((<$I>::MIN as i128) / (Self::POWER as i128)) as $I };
68            pub const INTEGRAL_MAX: $I =
69                if FRAC >= $bits { 0 } else { ((<$I>::MAX as i128) / (Self::POWER as i128)) as $I };
70
71
72
73            pub fn round(value: $I) -> $I {
74                crate::utility::round_half_to_even::<$I>(value, FRAC)
75            }
76
77            pub fn round_to_place(value: $I, place: usize) -> $I {
78                crate::utility::round_half_to_even::<$I>(value, place)
79            }
80        }
81    };
82}
83
84impl_fixed_format!(i8, true, 8);
85impl_fixed_format!(i16, true, 16);
86impl_fixed_format!(i32, true, 32);
87impl_fixed_format!(i64, true, 64);
88impl_fixed_format!(u8, false, 8);
89impl_fixed_format!(u16, false, 16);
90impl_fixed_format!(u32, false, 32);
91impl_fixed_format!(u64, false, 64);
92
93/// A trait for performing precision-preserving scaling and conversion between
94/// raw values of different fixed-point formats.
95pub trait ConvertValue<IDst, const FRAC_DST: usize> {
96    /// Converts `self` to a `Value` of another fixed-point format.
97    fn convert_value(self) -> Value<IDst, FRAC_DST>;
98}
99
100macro_rules! impl_convert_value {
101    (unsigned: $I_SRC:ty, $I_DST:ty) => {
102        impl<const FRAC_SRC: usize, const FRAC_DST: usize> ConvertValue<$I_DST, FRAC_DST>
103            for Value<$I_SRC, FRAC_SRC>
104        {
105            fn convert_value(self) -> Value<$I_DST, FRAC_DST> {
106                let val = self.value;
107                let promoted_value = val as i128;
108
109                let src_adj_frac = FixedFormat::<$I_SRC, FRAC_SRC>::ADJUSTED_FRACTIONAL_BITS;
110                let _dst_adj_frac = FixedFormat::<$I_DST, FRAC_DST>::ADJUSTED_FRACTIONAL_BITS;
111
112                let src_adj_factor = FixedFormat::<$I_SRC, FRAC_SRC>::ADJUSTMENT_FACTOR as i128;
113                let _dst_adj_factor = FixedFormat::<$I_DST, FRAC_DST>::ADJUSTMENT_FACTOR as i128;
114
115                if FRAC_SRC > FRAC_DST {
116                    let shifted_value = promoted_value / src_adj_factor;
117                    let delta = src_adj_frac - FRAC_DST;
118                    let rounded = crate::utility::round_half_to_even::<i128>(shifted_value, delta);
119                    let power = 1i128 << delta;
120                    let converted_value = rounded / power;
121                    Value { value: crate::utility::clamp_cast(converted_value) }
122                } else if FRAC_SRC < FRAC_DST {
123                    let delta = FRAC_DST - FRAC_SRC;
124                    let power = 1i128 << delta;
125                    let converted_value: $I_DST =
126                        crate::saturating_arithmetic::saturate_multiply(promoted_value, power);
127                    Value { value: converted_value }
128                } else {
129                    Value { value: crate::utility::clamp_cast(promoted_value) }
130                }
131            }
132        }
133    };
134    (mixed: $I_SRC:ty, $I_DST:ty) => {
135        impl<const FRAC_SRC: usize, const FRAC_DST: usize> ConvertValue<$I_DST, FRAC_DST>
136            for Value<$I_SRC, FRAC_SRC>
137        {
138            fn convert_value(self) -> Value<$I_DST, FRAC_DST> {
139                let val = self.value;
140                let promoted_value: i128 = crate::utility::clamp_cast(val);
141
142                let src_adj_frac = FixedFormat::<$I_SRC, FRAC_SRC>::ADJUSTED_FRACTIONAL_BITS;
143                let _dst_adj_frac = FixedFormat::<$I_DST, FRAC_DST>::ADJUSTED_FRACTIONAL_BITS;
144
145                let src_adj_factor = FixedFormat::<$I_SRC, FRAC_SRC>::ADJUSTMENT_FACTOR as i128;
146                let _dst_adj_factor = FixedFormat::<$I_DST, FRAC_DST>::ADJUSTMENT_FACTOR as i128;
147
148                if FRAC_SRC > FRAC_DST {
149                    let shifted_value = promoted_value / src_adj_factor;
150                    let delta = src_adj_frac - FRAC_DST;
151                    let rounded = crate::utility::round_half_to_even::<i128>(shifted_value, delta);
152                    let power = 1i128 << delta;
153                    let converted_value = rounded / power;
154                    Value { value: crate::utility::clamp_cast(converted_value) }
155                } else if FRAC_SRC < FRAC_DST {
156                    let delta = FRAC_DST - FRAC_SRC;
157                    let power = 1i128 << delta;
158                    let converted_value: $I_DST =
159                        crate::saturating_arithmetic::saturate_multiply(promoted_value, power);
160                    Value { value: converted_value }
161                } else {
162                    Value { value: crate::utility::clamp_cast(promoted_value) }
163                }
164            }
165        }
166    };
167}
168
169macro_rules! gen_convert_values {
170    (unsigned: [$($T:ty),*], $U_list:tt) => {
171        $(
172            gen_convert_values!(@unsigned_one $T, $U_list);
173        )*
174    };
175    (@unsigned_one $T:ty, [$($U:ty),*]) => {
176        $(
177            impl_convert_value!(unsigned: $T, $U);
178        )*
179    };
180
181    (mixed: [$($T:ty),*], $U_list:tt) => {
182        $(
183            gen_convert_values!(@mixed_one $T, $U_list);
184        )*
185    };
186    (@mixed_one $T:ty, [$($U:ty),*]) => {
187        $(
188            impl_convert_value!(mixed: $T, $U);
189        )*
190    };
191}
192
193gen_convert_values!(unsigned: [u8, u16, u32, u64], [u8, u16, u32, u64]);
194gen_convert_values!(mixed: [i8, i16, i32, i64], [i8, i16, i32, i64]);
195gen_convert_values!(mixed: [i8, i16, i32, i64], [u8, u16, u32, u64]);
196gen_convert_values!(mixed: [u8, u16, u32, u64], [i8, i16, i32, i64]);
197
198macro_rules! impl_convert_value_i128 {
199    ($I_SRC:ty) => {
200        impl<const FRAC_SRC: usize, const FRAC_DST: usize> ConvertValue<i128, FRAC_DST>
201            for Value<$I_SRC, FRAC_SRC>
202        {
203            fn convert_value(self) -> Value<i128, FRAC_DST> {
204                let val = self.value;
205                let promoted_value: i128 = crate::utility::clamp_cast(val);
206
207                let src_adj_frac = FixedFormat::<$I_SRC, FRAC_SRC>::ADJUSTED_FRACTIONAL_BITS;
208                let src_adj_factor = FixedFormat::<$I_SRC, FRAC_SRC>::ADJUSTMENT_FACTOR as i128;
209
210                if FRAC_SRC > FRAC_DST {
211                    let shifted_value = promoted_value / src_adj_factor;
212                    let delta = src_adj_frac - FRAC_DST;
213                    let rounded = crate::utility::round_half_to_even::<i128>(shifted_value, delta);
214                    let power = 1i128 << delta;
215                    let converted_value = rounded / power;
216                    Value { value: converted_value }
217                } else if FRAC_SRC < FRAC_DST {
218                    let delta = FRAC_DST - FRAC_SRC;
219                    let power = 1i128 << delta;
220                    let converted_value: i128 =
221                        crate::saturating_arithmetic::saturate_multiply(promoted_value, power);
222                    Value { value: converted_value }
223                } else {
224                    Value { value: promoted_value }
225                }
226            }
227        }
228    };
229}
230
231impl_convert_value_i128!(i8);
232impl_convert_value_i128!(i16);
233impl_convert_value_i128!(i32);
234impl_convert_value_i128!(i64);
235impl_convert_value_i128!(u8);
236impl_convert_value_i128!(u16);
237impl_convert_value_i128!(u32);
238impl_convert_value_i128!(u64);