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    pub const fn from_wire(val: i32) -> Self {
67        Self(val)
68    }
69
70    /// Converts this [`Fixed`] to the bits that should be set in the wire protocol.
71    pub const fn to_wire(self) -> i32 {
72        self.0
73    }
74
75    /// Converts this [`Fixed`] to an `f64`.
76    ///
77    /// This conversion is lossless.
78    pub const fn to_f64(self) -> f64 {
79        self.0 as f64 / fmul!()
80    }
81
82    /// Converts this [`Fixed`] to an `f32`.
83    ///
84    /// This conversion is lossy if there are more than 24 significant bits in this [`Fixed`].
85    pub const fn to_f32_lossy(self) -> f32 {
86        self.to_f64() as f32
87    }
88
89    /// Creates a [`Fixed`] from an `f64`.
90    ///
91    /// If the value cannot be represented exactly, the behavior is as when an `f64` is cast to an
92    /// integer. That is
93    ///
94    /// - Values are rounded towards 0.
95    /// - `NaN` returns [`Fixed::ZERO`].
96    /// - Values larger than the maximum return [`Fixed::MAX`].
97    /// - Values smaller than the minimum return [`Fixed::MIN`].
98    pub const fn from_f64_lossy(val: f64) -> Self {
99        Self((val * fmul!()) as i32)
100    }
101
102    /// Creates a [`Fixed`] from an `f32`.
103    ///
104    /// The conversion behavior is the same as for [`Fixed::from_f64_lossy`].
105    pub const fn from_f32_lossy(val: f32) -> Self {
106        Self((val as f64 * fmul!()) as i32)
107    }
108
109    /// Creates a [`Fixed`] from an `i32`.
110    ///
111    /// Values outside of the representable range are clamped to [`Fixed::MIN`] and [`Fixed::MAX`].
112    pub const fn from_i32_saturating(val: i32) -> Self {
113        Self(val.saturating_mul(imul!()))
114    }
115
116    /// Creates a [`Fixed`] from an `i64`.
117    ///
118    /// Values outside of the representable range are clamped to [`Fixed::MIN`] and [`Fixed::MAX`].
119    pub const fn from_i64_saturating(val: i64) -> Self {
120        let val = val.saturating_mul(imul!());
121        if val > i32::MAX as i64 {
122            Self(i32::MAX)
123        } else if val < i32::MIN as i64 {
124            Self(i32::MIN)
125        } else {
126            Self(val as i32)
127        }
128    }
129
130    /// Converts this [`Fixed`] to an `i32`.
131    ///
132    /// The conversion rounds towards the nearest integer and half-way away from 0.
133    pub const fn to_i32_round_towards_nearest(self) -> i32 {
134        if self.0 >= 0 {
135            ((self.0 as i64 + (imul!() / 2)) / imul!()) as i32
136        } else {
137            ((self.0 as i64 - (imul!() / 2)) / imul!()) as i32
138        }
139    }
140
141    /// Converts this [`Fixed`] to an `i32`.
142    ///
143    /// The conversion rounds towards zero.
144    pub const fn to_i32_round_towards_zero(self) -> i32 {
145        (self.0 as i64 / imul!()) as i32
146    }
147
148    /// Converts this [`Fixed`] to an `i32`.
149    ///
150    /// The conversion rounds towards minus infinity.
151    pub const fn to_i32_floor(self) -> i32 {
152        self.0 >> shift!()
153    }
154
155    /// Converts this [`Fixed`] to an `i32`.
156    ///
157    /// The conversion rounds towards infinity.
158    pub const fn to_i32_ceil(self) -> i32 {
159        ((self.0 as i64 + imul!() - 1) >> shift!()) as i32
160    }
161}
162
163macro_rules! from {
164    ($t:ty) => {
165        impl From<$t> for Fixed {
166            fn from(value: $t) -> Self {
167                Self(value as i32 * imul!())
168            }
169        }
170    };
171}
172
173from!(i8);
174from!(u8);
175from!(i16);
176from!(u16);
177
178impl From<Fixed> for f64 {
179    fn from(value: Fixed) -> Self {
180        value.to_f64()
181    }
182}
183
184impl Debug for Fixed {
185    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
186        Debug::fmt(&self.to_f64(), f)
187    }
188}
189
190impl Display for Fixed {
191    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
192        Display::fmt(&self.to_f64(), f)
193    }
194}
195
196macro_rules! forward_simple_immutable_binop {
197    ($slf:ty, $arg:ty, $big_name:ident, $small_name:ident, $op:tt) => {
198        impl $big_name<$arg> for $slf {
199            type Output = Fixed;
200
201            fn $small_name(self, rhs: $arg) -> Self::Output {
202                Fixed(self.0 $op rhs.0)
203            }
204        }
205    };
206}
207
208macro_rules! forward_simple_binop {
209    ($big_name:ident, $small_name:ident, $op:tt, $assign_big_name:ident, $assign_small_name:ident, $assign_op:tt) => {
210        forward_simple_immutable_binop!(Fixed,  Fixed,  $big_name, $small_name, $op);
211        forward_simple_immutable_binop!(Fixed,  &Fixed, $big_name, $small_name, $op);
212        forward_simple_immutable_binop!(&Fixed, Fixed,  $big_name, $small_name, $op);
213        forward_simple_immutable_binop!(&Fixed, &Fixed, $big_name, $small_name, $op);
214
215        impl $assign_big_name for Fixed {
216            fn $assign_small_name(&mut self, rhs: Self) {
217                self.0 $assign_op rhs.0;
218            }
219        }
220    };
221}
222
223forward_simple_binop!(Add,    add,    +, AddAssign,    add_assign,    +=);
224forward_simple_binop!(Sub,    sub,    -, SubAssign,    sub_assign,    -=);
225forward_simple_binop!(Rem,    rem,    %, RemAssign,    rem_assign,    %=);
226forward_simple_binop!(BitAnd, bitand, &, BitAndAssign, bitand_assign, &=);
227forward_simple_binop!(BitOr,  bitor,  |, BitOrAssign,  bitor_assign,  |=);
228forward_simple_binop!(BitXor, bitxor, ^, BitXorAssign, bitxor_assign, ^=);
229
230#[inline(always)]
231const fn mul(slf: i32, rhs: i32) -> i32 {
232    (slf as i64 * rhs as i64 / imul!()) as i32
233}
234
235#[inline(always)]
236const fn div(slf: i32, rhs: i32) -> i32 {
237    (slf as i64 * imul!() / rhs as i64) as i32
238}
239
240macro_rules! forward_complex_immutable_binop {
241    ($slf:ty, $arg:ty, $big_name:ident, $small_name:ident) => {
242        impl $big_name<$arg> for $slf {
243            type Output = Fixed;
244
245            fn $small_name(self, rhs: $arg) -> Self::Output {
246                Fixed($small_name(self.0, rhs.0))
247            }
248        }
249    };
250}
251
252macro_rules! forward_complex_binop {
253    ($big_name:ident, $small_name:ident, $assign_big_name:ident, $assign_small_name:ident) => {
254        forward_complex_immutable_binop!(Fixed, Fixed, $big_name, $small_name);
255        forward_complex_immutable_binop!(Fixed, &Fixed, $big_name, $small_name);
256        forward_complex_immutable_binop!(&Fixed, Fixed, $big_name, $small_name);
257        forward_complex_immutable_binop!(&Fixed, &Fixed, $big_name, $small_name);
258
259        impl $assign_big_name for Fixed {
260            fn $assign_small_name(&mut self, rhs: Self) {
261                self.0 = $small_name(self.0, rhs.0);
262            }
263        }
264    };
265}
266
267forward_complex_binop!(Mul, mul, MulAssign, mul_assign);
268forward_complex_binop!(Div, div, DivAssign, div_assign);
269
270macro_rules! forward_shiftop {
271    ($big_name:ident, $small_name:ident, $arg:ty, $op:tt, $assign_big_name:ident, $assign_small_name:ident, $assign_op:tt) => {
272        impl $big_name<$arg> for Fixed {
273            type Output = Fixed;
274
275            fn $small_name(self, rhs: $arg) -> Self::Output {
276                Fixed(self.0 $op rhs)
277            }
278        }
279
280        impl $big_name<$arg> for &Fixed {
281            type Output = Fixed;
282
283            fn $small_name(self, rhs: $arg) -> Self::Output {
284                Fixed(self.0 $op rhs)
285            }
286        }
287
288        impl $assign_big_name<$arg> for Fixed {
289            fn $assign_small_name(&mut self, rhs: $arg) {
290                self.0 $assign_op rhs;
291            }
292        }
293    };
294}
295
296macro_rules! forward_shift {
297    ($arg:ty) => {
298        forward_shiftop!(Shl, shl, $arg,  <<, ShlAssign, shl_assign, <<=);
299        forward_shiftop!(Shl, shl, &$arg, <<, ShlAssign, shl_assign, <<=);
300        forward_shiftop!(Shr, shr, $arg,  >>, ShrAssign, shr_assign, >>=);
301        forward_shiftop!(Shr, shr, &$arg, >>, ShrAssign, shr_assign, >>=);
302    }
303}
304
305forward_shift!(u8);
306forward_shift!(i8);
307forward_shift!(u16);
308forward_shift!(i16);
309forward_shift!(u32);
310forward_shift!(i32);
311forward_shift!(u64);
312forward_shift!(i64);
313forward_shift!(u128);
314forward_shift!(i128);
315forward_shift!(usize);
316forward_shift!(isize);
317
318macro_rules! forward_immutable_unop {
319    ($slf:ty, $big_name:ident, $small_name:ident, $op:tt) => {
320        impl $big_name for $slf {
321            type Output = Fixed;
322
323            fn $small_name(self) -> Self::Output {
324                Fixed($op self.0)
325            }
326        }
327    };
328}
329
330macro_rules! forward_unop {
331    ($big_name:ident, $small_name:ident, $op:tt) => {
332        forward_immutable_unop!(Fixed, $big_name, $small_name, $op);
333        forward_immutable_unop!(&Fixed, $big_name, $small_name, $op);
334    };
335}
336
337forward_unop!(Neg, neg, -);
338forward_unop!(Not, not, !);