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#[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 pub const MAX: Self = Self(i32::MAX);
46
47 pub const MIN: Self = Self(i32::MIN);
49
50 pub const ZERO: Self = Self(0);
52
53 pub const ONE: Self = Self::from_i32_saturating(1);
55
56 pub const TWO: Self = Self::from_i32_saturating(2);
58
59 pub const EPSILON: Self = Self(1);
61
62 pub const NEGATIVE_EPSILON: Self = Self(!0);
64
65 #[inline]
67 pub const fn from_wire(val: i32) -> Self {
68 Self(val)
69 }
70
71 #[inline]
73 pub const fn to_wire(self) -> i32 {
74 self.0
75 }
76
77 #[inline]
81 pub const fn to_f64(self) -> f64 {
82 self.0 as f64 / fmul!()
83 }
84
85 #[inline]
89 pub const fn to_f32_lossy(self) -> f32 {
90 self.to_f64() as f32
91 }
92
93 #[inline]
103 pub const fn from_f64_lossy(val: f64) -> Self {
104 Self((val * fmul!()) as i32)
105 }
106
107 #[inline]
111 pub const fn from_f32_lossy(val: f32) -> Self {
112 Self((val as f64 * fmul!()) as i32)
113 }
114
115 #[inline]
119 pub const fn from_i32_saturating(val: i32) -> Self {
120 Self(val.saturating_mul(imul!()))
121 }
122
123 #[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 #[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 #[inline]
154 pub const fn to_i32_round_towards_zero(self) -> i32 {
155 (self.0 as i64 / imul!()) as i32
156 }
157
158 #[inline]
162 pub const fn to_i32_floor(self) -> i32 {
163 self.0 >> shift!()
164 }
165
166 #[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, !);