wl_proxy/
fixed.rs

1//! A signed 24.8 fixed-point number used in the wayland protocol.
2
3#[cfg(test)]
4mod tests;
5
6use std::{
7    fmt::{Debug, Display, Formatter},
8    ops::{
9        Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div,
10        DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub,
11        SubAssign,
12    },
13};
14
15/// A signed 24.8 fixed-point number used in the wayland protocol.
16///
17/// This is a signed decimal type which offers a sign bit, 23 bits of integer precision and 8 bits
18/// of decimal precision.
19///
20/// # Arithmetic operations
21///
22/// This type implements all of the usual arithmetic operations for numbers. On overflow, they
23/// behave like the standard library operations except that multiplication and division always use
24/// wrapping semantics.
25#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
26#[repr(transparent)]
27pub struct Fixed(i32);
28
29macro_rules! fmul {
30    () => {
31        256.0
32    };
33}
34macro_rules! imul {
35    () => {
36        256
37    };
38}
39macro_rules! shift {
40    () => {
41        8
42    };
43}
44
45impl Fixed {
46    /// The largest [`Fixed`].
47    pub const MAX: Self = Self(i32::MAX);
48
49    /// The smallest [`Fixed`].
50    pub const MIN: Self = Self(i32::MIN);
51
52    /// The 0 [`Fixed`].
53    pub const ZERO: Self = Self(0);
54
55    /// The 1 [`Fixed`].
56    pub const ONE: Self = Self::from_i32_saturating(1);
57
58    /// The 2 [`Fixed`].
59    pub const TWO: Self = Self::from_i32_saturating(2);
60
61    /// The smallest positive [`Fixed`].
62    pub const EPSILON: Self = Self(1);
63
64    /// The largest negative [`Fixed`].
65    pub const NEGATIVE_EPSILON: Self = Self(!0);
66
67    /// Creates a [`Fixed`] from the raw bits that appear in the wire protocol.
68    #[inline]
69    pub const fn from_wire(val: i32) -> Self {
70        Self(val)
71    }
72
73    /// Converts this [`Fixed`] to the bits that should be set in the wire protocol.
74    #[inline]
75    pub const fn to_wire(self) -> i32 {
76        self.0
77    }
78
79    /// Converts this [`Fixed`] to an `f64`.
80    ///
81    /// This conversion is lossless.
82    #[inline]
83    pub const fn to_f64(self) -> f64 {
84        self.0 as f64 / fmul!()
85    }
86
87    /// Converts this [`Fixed`] to an `f32`.
88    ///
89    /// This conversion is lossy if there are more than 24 significant bits in this [`Fixed`].
90    #[inline]
91    pub const fn to_f32_lossy(self) -> f32 {
92        self.to_f64() as f32
93    }
94
95    /// Creates a [`Fixed`] from an `f64`.
96    ///
97    /// If the value cannot be represented exactly, the behavior is as when an `f64` is cast to an
98    /// integer. That is
99    ///
100    /// - Values are rounded towards 0.
101    /// - `NaN` returns [`Fixed::ZERO`].
102    /// - Values larger than the maximum return [`Fixed::MAX`].
103    /// - Values smaller than the minimum return [`Fixed::MIN`].
104    #[inline]
105    pub const fn from_f64_lossy(val: f64) -> Self {
106        Self((val * fmul!()) as i32)
107    }
108
109    /// Creates a [`Fixed`] from an `f32`.
110    ///
111    /// The conversion behavior is the same as for [`Fixed::from_f64_lossy`].
112    #[inline]
113    pub const fn from_f32_lossy(val: f32) -> Self {
114        Self((val as f64 * fmul!()) as i32)
115    }
116
117    /// Creates a [`Fixed`] from an `i32`.
118    ///
119    /// Values outside of the representable range are clamped to [`Fixed::MIN`] and [`Fixed::MAX`].
120    #[inline]
121    pub const fn from_i32_saturating(val: i32) -> Self {
122        Self(val.saturating_mul(imul!()))
123    }
124
125    /// Creates a [`Fixed`] from an `i64`.
126    ///
127    /// Values outside of the representable range are clamped to [`Fixed::MIN`] and [`Fixed::MAX`].
128    #[inline]
129    pub const fn from_i64_saturating(val: i64) -> Self {
130        let val = val.saturating_mul(imul!());
131        if val > i32::MAX as i64 {
132            Self(i32::MAX)
133        } else if val < i32::MIN as i64 {
134            Self(i32::MIN)
135        } else {
136            Self(val as i32)
137        }
138    }
139
140    /// Converts this [`Fixed`] to an `i32`.
141    ///
142    /// The conversion rounds towards the nearest integer and half-way away from 0.
143    #[inline]
144    pub const fn to_i32_round_towards_nearest(self) -> i32 {
145        if self.0 >= 0 {
146            ((self.0 as i64 + (imul!() / 2)) / imul!()) as i32
147        } else {
148            ((self.0 as i64 - (imul!() / 2)) / imul!()) as i32
149        }
150    }
151
152    /// Converts this [`Fixed`] to an `i32`.
153    ///
154    /// The conversion rounds towards zero.
155    #[inline]
156    pub const fn to_i32_round_towards_zero(self) -> i32 {
157        (self.0 as i64 / imul!()) as i32
158    }
159
160    /// Converts this [`Fixed`] to an `i32`.
161    ///
162    /// The conversion rounds towards minus infinity.
163    #[inline]
164    pub const fn to_i32_floor(self) -> i32 {
165        self.0 >> shift!()
166    }
167
168    /// Converts this [`Fixed`] to an `i32`.
169    ///
170    /// The conversion rounds towards infinity.
171    #[inline]
172    pub const fn to_i32_ceil(self) -> i32 {
173        ((self.0 as i64 + imul!() - 1) >> shift!()) as i32
174    }
175}
176
177macro_rules! from {
178    ($t:ty) => {
179        impl From<$t> for Fixed {
180            #[inline]
181            fn from(value: $t) -> Self {
182                Self(value as i32 * imul!())
183            }
184        }
185    };
186}
187
188from!(i8);
189from!(u8);
190from!(i16);
191from!(u16);
192
193impl From<Fixed> for f64 {
194    #[inline]
195    fn from(value: Fixed) -> Self {
196        value.to_f64()
197    }
198}
199
200impl Debug for Fixed {
201    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
202        Debug::fmt(&self.to_f64(), f)
203    }
204}
205
206impl Display for Fixed {
207    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
208        Display::fmt(&self.to_f64(), f)
209    }
210}
211
212macro_rules! forward_simple_immutable_binop {
213    ($slf:ty, $arg:ty, $big_name:ident, $small_name:ident, $op:tt) => {
214        impl $big_name<$arg> for $slf {
215            type Output = Fixed;
216
217            #[inline]
218            fn $small_name(self, rhs: $arg) -> Self::Output {
219                Fixed(self.0 $op rhs.0)
220            }
221        }
222    };
223}
224
225macro_rules! forward_simple_binop {
226    ($big_name:ident, $small_name:ident, $op:tt, $assign_big_name:ident, $assign_small_name:ident, $assign_op:tt) => {
227        forward_simple_immutable_binop!(Fixed,  Fixed,  $big_name, $small_name, $op);
228        forward_simple_immutable_binop!(Fixed,  &Fixed, $big_name, $small_name, $op);
229        forward_simple_immutable_binop!(&Fixed, Fixed,  $big_name, $small_name, $op);
230        forward_simple_immutable_binop!(&Fixed, &Fixed, $big_name, $small_name, $op);
231
232        impl $assign_big_name for Fixed {
233            #[inline]
234            fn $assign_small_name(&mut self, rhs: Self) {
235                self.0 $assign_op rhs.0;
236            }
237        }
238    };
239}
240
241forward_simple_binop!(Add,    add,    +, AddAssign,    add_assign,    +=);
242forward_simple_binop!(Sub,    sub,    -, SubAssign,    sub_assign,    -=);
243forward_simple_binop!(Rem,    rem,    %, RemAssign,    rem_assign,    %=);
244forward_simple_binop!(BitAnd, bitand, &, BitAndAssign, bitand_assign, &=);
245forward_simple_binop!(BitOr,  bitor,  |, BitOrAssign,  bitor_assign,  |=);
246forward_simple_binop!(BitXor, bitxor, ^, BitXorAssign, bitxor_assign, ^=);
247
248#[inline(always)]
249const fn mul(slf: i32, rhs: i32) -> i32 {
250    (slf as i64 * rhs as i64 / imul!()) as i32
251}
252
253#[inline(always)]
254const fn div(slf: i32, rhs: i32) -> i32 {
255    (slf as i64 * imul!() / rhs as i64) as i32
256}
257
258macro_rules! forward_complex_immutable_binop {
259    ($slf:ty, $arg:ty, $big_name:ident, $small_name:ident) => {
260        impl $big_name<$arg> for $slf {
261            type Output = Fixed;
262
263            #[inline]
264            fn $small_name(self, rhs: $arg) -> Self::Output {
265                Fixed($small_name(self.0, rhs.0))
266            }
267        }
268    };
269}
270
271macro_rules! forward_complex_binop {
272    ($big_name:ident, $small_name:ident, $assign_big_name:ident, $assign_small_name:ident) => {
273        forward_complex_immutable_binop!(Fixed, Fixed, $big_name, $small_name);
274        forward_complex_immutable_binop!(Fixed, &Fixed, $big_name, $small_name);
275        forward_complex_immutable_binop!(&Fixed, Fixed, $big_name, $small_name);
276        forward_complex_immutable_binop!(&Fixed, &Fixed, $big_name, $small_name);
277
278        impl $assign_big_name for Fixed {
279            #[inline]
280            fn $assign_small_name(&mut self, rhs: Self) {
281                self.0 = $small_name(self.0, rhs.0);
282            }
283        }
284    };
285}
286
287forward_complex_binop!(Mul, mul, MulAssign, mul_assign);
288forward_complex_binop!(Div, div, DivAssign, div_assign);
289
290macro_rules! forward_shiftop {
291    ($big_name:ident, $small_name:ident, $arg:ty, $op:tt, $assign_big_name:ident, $assign_small_name:ident, $assign_op:tt) => {
292        impl $big_name<$arg> for Fixed {
293            type Output = Fixed;
294
295            #[inline]
296            fn $small_name(self, rhs: $arg) -> Self::Output {
297                Fixed(self.0 $op rhs)
298            }
299        }
300
301        impl $big_name<$arg> for &Fixed {
302            type Output = Fixed;
303
304            #[inline]
305            fn $small_name(self, rhs: $arg) -> Self::Output {
306                Fixed(self.0 $op rhs)
307            }
308        }
309
310        impl $assign_big_name<$arg> for Fixed {
311            #[inline]
312            fn $assign_small_name(&mut self, rhs: $arg) {
313                self.0 $assign_op rhs;
314            }
315        }
316    };
317}
318
319macro_rules! forward_shift {
320    ($arg:ty) => {
321        forward_shiftop!(Shl, shl, $arg,  <<, ShlAssign, shl_assign, <<=);
322        forward_shiftop!(Shl, shl, &$arg, <<, ShlAssign, shl_assign, <<=);
323        forward_shiftop!(Shr, shr, $arg,  >>, ShrAssign, shr_assign, >>=);
324        forward_shiftop!(Shr, shr, &$arg, >>, ShrAssign, shr_assign, >>=);
325    }
326}
327
328forward_shift!(u8);
329forward_shift!(i8);
330forward_shift!(u16);
331forward_shift!(i16);
332forward_shift!(u32);
333forward_shift!(i32);
334forward_shift!(u64);
335forward_shift!(i64);
336forward_shift!(u128);
337forward_shift!(i128);
338forward_shift!(usize);
339forward_shift!(isize);
340
341macro_rules! forward_immutable_unop {
342    ($slf:ty, $big_name:ident, $small_name:ident, $op:tt) => {
343        impl $big_name for $slf {
344            type Output = Fixed;
345
346            #[inline]
347            fn $small_name(self) -> Self::Output {
348                Fixed($op self.0)
349            }
350        }
351    };
352}
353
354macro_rules! forward_unop {
355    ($big_name:ident, $small_name:ident, $op:tt) => {
356        forward_immutable_unop!(Fixed, $big_name, $small_name, $op);
357        forward_immutable_unop!(&Fixed, $big_name, $small_name, $op);
358    };
359}
360
361forward_unop!(Neg, neg, -);
362forward_unop!(Not, not, !);