wl_client/
fixed.rs

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