fixed/prim_traits.rs
1// Copyright © 2018–2025 Trevor Spiteri
2
3// This library is free software: you can redistribute it and/or modify it under
4// the terms of either
5//
6// * the Apache License, Version 2.0 or
7// * the MIT License
8//
9// at your option.
10//
11// You should have recieved copies of the Apache License and the MIT License
12// along with the library. If not, see
13// <https://www.apache.org/licenses/LICENSE-2.0> and
14// <https://opensource.org/licenses/MIT>.
15
16#![allow(deprecated)]
17
18use crate::float_helper;
19use crate::helpers::{FloatKind, FromFloatHelper, Private};
20use crate::int_helper::IntFixed;
21use crate::traits::{Fixed, FixedEquiv, FromFixed, ToFixed};
22use crate::types::extra::U0;
23use crate::{
24 F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
25 FixedU64, FixedU128,
26};
27use bytemuck::TransparentWrapper;
28use half::{bf16 as half_bf16, f16 as half_f16};
29
30impl ToFixed for bool {
31 /// Converts a [`bool`] to a fixed-point number.
32 ///
33 /// # Panics
34 ///
35 /// When debug assertions are enabled, panics if the value does
36 /// not fit. When debug assertions are not enabled, the wrapped
37 /// value can be returned, but it is not considered a breaking
38 /// change if in the future it panics; if wrapping is required use
39 /// [`wrapping_to_fixed`] instead.
40 ///
41 /// [`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
42 #[inline]
43 #[track_caller]
44 fn to_fixed<F: Fixed>(self) -> F {
45 ToFixed::to_fixed(u8::from(self))
46 }
47
48 /// Converts a [`bool`] to a fixed-point number if it fits, otherwise returns [`None`].
49 #[inline]
50 fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
51 ToFixed::checked_to_fixed(u8::from(self))
52 }
53
54 /// Convert a [`bool`] to a fixed-point number, saturating if it does not fit.
55 #[inline]
56 fn saturating_to_fixed<F: Fixed>(self) -> F {
57 ToFixed::saturating_to_fixed(u8::from(self))
58 }
59
60 /// Converts a [`bool`] to a fixed-point number, wrapping if it does not fit.
61 #[inline]
62 fn wrapping_to_fixed<F: Fixed>(self) -> F {
63 ToFixed::wrapping_to_fixed(u8::from(self))
64 }
65
66 /// Converts a [`bool`] to a fixed-point number.
67 ///
68 /// Returns a [tuple] of the fixed-point number and a [`bool`]
69 /// indicating whether an overflow has occurred. On overflow, the
70 /// wrapped value is returned.
71 #[inline]
72 fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
73 ToFixed::overflowing_to_fixed(u8::from(self))
74 }
75
76 /// Converts a [`bool`] to a fixed-point number, panicking if it
77 /// does not fit.
78 ///
79 /// # Panics
80 ///
81 /// Panics if the value does not fit, even when debug assertions
82 /// are not enabled.
83 #[inline]
84 #[track_caller]
85 fn unwrapped_to_fixed<F: Fixed>(self) -> F {
86 ToFixed::unwrapped_to_fixed(u8::from(self))
87 }
88}
89
90macro_rules! impl_int {
91 ($Int:ident $(, $Equiv:ident)?) => {
92 impl FromFixed for $Int {
93 /// Converts a fixed-point number to an integer.
94 ///
95 /// Any fractional bits are discarded, which rounds towards −∞.
96 ///
97 /// # Panics
98 ///
99 /// When debug assertions are enabled, panics if the value
100 /// does not fit. When debug assertions are not enabled,
101 /// the wrapped value can be returned, but it is not
102 /// considered a breaking change if in the future it
103 /// panics; if wrapping is required use
104 /// [`wrapping_from_fixed`] instead.
105 ///
106 /// [`wrapping_from_fixed`]: FromFixed::wrapping_from_fixed
107 #[inline]
108 #[track_caller]
109 fn from_fixed<F: Fixed>(src: F) -> Self {
110 IntFixed::<$Int>::int(FromFixed::from_fixed(src))
111 }
112
113 /// Converts a fixed-point number to an integer if it fits, otherwise returns [`None`].
114 ///
115 /// Any fractional bits are discarded, which rounds towards −∞.
116 #[inline]
117 fn checked_from_fixed<F: Fixed>(src: F) -> Option<Self> {
118 FromFixed::checked_from_fixed(src).map(IntFixed::<$Int>::int)
119 }
120
121 /// Converts a fixed-point number to an integer, saturating if it does not fit.
122 ///
123 /// Any fractional bits are discarded, which rounds towards −∞.
124 #[inline]
125 fn saturating_from_fixed<F: Fixed>(src: F) -> Self {
126 IntFixed::<$Int>::int(FromFixed::saturating_from_fixed(src))
127 }
128
129 /// Converts a fixed-point number to an integer, wrapping if it does not fit.
130 ///
131 /// Any fractional bits are discarded, which rounds towards −∞.
132 #[inline]
133 fn wrapping_from_fixed<F: Fixed>(src: F) -> Self {
134 IntFixed::<$Int>::int(FromFixed::wrapping_from_fixed(src))
135 }
136
137 /// Converts a fixed-point number to an integer.
138 ///
139 /// Returns a [tuple] of the value and a [`bool`] indicating whether
140 /// an overflow has occurred. On overflow, the wrapped value is
141 /// returned.
142 ///
143 /// Any fractional bits are discarded, which rounds towards −∞.
144 #[inline]
145 fn overflowing_from_fixed<F: Fixed>(src: F) -> (Self, bool) {
146 let (fixed, overflow) = FromFixed::overflowing_from_fixed(src);
147 (IntFixed::<$Int>::int(fixed), overflow)
148 }
149
150 /// Converts a fixed-point number to an integer, panicking if it does not fit.
151 ///
152 /// Any fractional bits are discarded, which rounds towards −∞.
153 ///
154 /// # Panics
155 ///
156 /// Panics if the value
157 /// does not fit, even when debug assertions are not enabled.
158 #[inline]
159 #[track_caller]
160 fn unwrapped_from_fixed<F: Fixed>(src: F) -> Self {
161 IntFixed::<$Int>::int(FromFixed::unwrapped_from_fixed(src))
162 }
163 }
164
165 impl ToFixed for $Int {
166 /// Converts an integer to a fixed-point number.
167 ///
168 /// # Panics
169 ///
170 /// When debug assertions are enabled, panics if the value
171 /// does not fit. When debug assertions are not enabled,
172 /// the wrapped value can be returned, but it is not
173 /// considered a breaking change if in the future it
174 /// panics; if wrapping is required use
175 /// [`wrapping_to_fixed`] instead.
176 ///
177 /// [`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
178 #[inline]
179 #[track_caller]
180 fn to_fixed<F: Fixed>(self) -> F {
181 ToFixed::to_fixed(IntFixed(self).fixed())
182 }
183
184 /// Converts an integer to a fixed-point number if it fits, otherwise returns [`None`].
185 #[inline]
186 fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
187 ToFixed::checked_to_fixed(IntFixed(self).fixed())
188 }
189
190 /// Converts an integer to a fixed-point number, saturating if it does not fit.
191 #[inline]
192 fn saturating_to_fixed<F: Fixed>(self) -> F {
193 ToFixed::saturating_to_fixed(IntFixed(self).fixed())
194 }
195
196 /// Converts an integer to a fixed-point number, wrapping if it does not fit.
197 #[inline]
198 fn wrapping_to_fixed<F: Fixed>(self) -> F {
199 ToFixed::wrapping_to_fixed(IntFixed(self).fixed())
200 }
201
202 /// Converts an integer to a fixed-point number.
203 ///
204 /// Returns a [tuple] of the fixed-point number and a [`bool`]
205 /// indicating whether an overflow has occurred. On overflow, the
206 /// wrapped value is returned.
207 #[inline]
208 fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
209 ToFixed::overflowing_to_fixed(IntFixed(self).fixed())
210 }
211
212 /// Converts an integer to a fixed-point number, panicking if it does not fit.
213 ///
214 /// # Panics
215 ///
216 /// Panics if the value does not fit, even when debug
217 /// assertions are not enabled.
218 #[inline]
219 #[track_caller]
220 fn unwrapped_to_fixed<F: Fixed>(self) -> F {
221 ToFixed::unwrapped_to_fixed(IntFixed(self).fixed())
222 }
223 }
224
225 $(
226 impl FixedEquiv for $Int {
227 type Equiv = $Equiv<U0>;
228
229 #[inline]
230 fn to_fixed_equiv(self) -> $Equiv<U0> {
231 $Equiv::from_bits(self)
232 }
233
234 #[inline]
235 fn as_fixed_equiv(&self) -> &$Equiv<U0> {
236 $Equiv::wrap_ref(self)
237 }
238
239 #[inline]
240 fn as_fixed_equiv_mut(&mut self) -> &mut $Equiv<U0> {
241 $Equiv::wrap_mut(self)
242 }
243
244 #[inline]
245 fn from_fixed_equiv(f: $Equiv<U0>) -> $Int {
246 f.to_bits()
247 }
248
249 #[inline]
250 fn ref_from_fixed_equiv(f: &$Equiv<U0>) -> &$Int {
251 &f.bits
252 }
253
254 #[inline]
255 fn mut_from_fixed_equiv(f: &mut $Equiv<U0>) -> &mut $Int {
256 &mut f.bits
257 }
258 }
259 )*
260 };
261}
262
263impl_int! { i8, FixedI8 }
264impl_int! { i16, FixedI16 }
265impl_int! { i32, FixedI32 }
266impl_int! { i64, FixedI64 }
267impl_int! { i128, FixedI128 }
268impl_int! { isize }
269impl_int! { u8, FixedU8 }
270impl_int! { u16, FixedU16 }
271impl_int! { u32, FixedU32 }
272impl_int! { u64, FixedU64 }
273impl_int! { u128, FixedU128 }
274impl_int! { usize }
275
276macro_rules! impl_float {
277 ($Float:ident, $link:expr, $overflows_fmt:expr, $overflows_filt:expr) => {
278 impl FromFixed for $Float {
279 /// Converts a fixed-point number to a floating-point number.
280 ///
281 /// Rounding is to the nearest, with ties rounded to even.
282 ///
283 /// # Panics
284 ///
285 /// When debug assertions are enabled, panics if the value
286 /// does not fit. When debug assertions are not enabled,
287 /// the wrapped value can be returned, but it is not
288 /// considered a breaking change if in the future it
289 /// panics; if wrapping is required use
290 /// [`wrapping_from_fixed`] instead.
291 ///
292 /// [`wrapping_from_fixed`]: FromFixed::wrapping_from_fixed
293 #[inline]
294 #[track_caller]
295 fn from_fixed<F: Fixed>(src: F) -> Self {
296 let helper = src.to_float_helper(Private);
297 float_helper::$Float::from_to_float_helper(helper, F::FRAC_NBITS, F::INT_NBITS)
298 }
299
300 /// Converts a fixed-point number to a floating-point
301 /// number if it fits, otherwise returns [`None`].
302 ///
303 /// Rounding is to the nearest, with ties rounded to even.
304 #[inline]
305 fn checked_from_fixed<F: Fixed>(src: F) -> Option<Self> {
306 Some(FromFixed::from_fixed(src))
307 }
308
309 /// Converts a fixed-point number to a floating-point
310 /// number, saturating if it does not fit.
311 ///
312 /// Rounding is to the nearest, with ties rounded to even.
313 #[inline]
314 fn saturating_from_fixed<F: Fixed>(src: F) -> Self {
315 FromFixed::from_fixed(src)
316 }
317
318 /// Converts a fixed-point number to a floating-point
319 /// number, wrapping if it does not fit.
320 ///
321 /// Rounding is to the nearest, with ties rounded to even.
322 #[inline]
323 fn wrapping_from_fixed<F: Fixed>(src: F) -> Self {
324 FromFixed::from_fixed(src)
325 }
326
327 /// Converts a fixed-point number to a floating-point number.
328 ///
329 /// Returns a [tuple] of the value and a [`bool`]
330 /// indicating whether an overflow has occurred. On
331 /// overflow, the wrapped value is returned.
332 ///
333 /// Rounding is to the nearest, with ties rounded to even.
334 #[inline]
335 fn overflowing_from_fixed<F: Fixed>(src: F) -> (Self, bool) {
336 (FromFixed::from_fixed(src), false)
337 }
338
339 /// Converts a fixed-point number to a floating-point
340 /// number, panicking if it does not fit.
341 ///
342 /// Rounding is to the nearest, with ties rounded to even.
343 ///
344 /// # Panics
345 ///
346 /// Panics if the value does not fit, even when debug
347 /// assertions are not enabled.
348 #[inline]
349 #[track_caller]
350 fn unwrapped_from_fixed<F: Fixed>(src: F) -> Self {
351 FromFixed::from_fixed(src)
352 }
353 }
354
355 impl ToFixed for $Float {
356 comment! {
357 "Converts a floating-point number to a fixed-point number.
358
359Rounding is to the nearest, with ties rounded to even.
360
361# Panics
362
363Panics if `self` is not [finite].
364
365When debug assertions are enabled, also panics if the value does not
366fit. When debug assertions are not enabled, the wrapped value can be
367returned, but it is not considered a breaking change if in the future
368it panics; if wrapping is required use [`wrapping_to_fixed`] instead.
369
370[`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
371[finite]: ", $link, "::is_finite
372";
373 #[inline]
374 #[track_caller]
375 fn to_fixed<F: Fixed>(self) -> F {
376 let (wrapped, overflow) = ToFixed::overflowing_to_fixed(self);
377 debug_assert!(!overflow, $overflows_fmt, $overflows_filt(self));
378 wrapped
379 }
380 }
381
382 /// Converts a floating-point number to a fixed-point
383 /// number if it fits, otherwise returns [`None`].
384 ///
385 /// Rounding is to the nearest, with ties rounded to even.
386 #[inline]
387 fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
388 let kind = float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
389 match kind {
390 FloatKind::Finite { .. } => {
391 let helper = FromFloatHelper { kind };
392 match F::overflowing_from_float_helper(Private, helper) {
393 (_, true) => None,
394 (wrapped, false) => Some(wrapped),
395 }
396 }
397 _ => None,
398 }
399 }
400
401 comment! {
402 "Converts a floating-point number to a fixed-point
403number, saturating if it does not fit.
404
405Rounding is to the nearest, with ties rounded to even.
406
407# Panics
408
409Panics if `self` is [NaN].
410
411[NaN]: ", $link, "::is_nan
412";
413 #[inline]
414 #[track_caller]
415 fn saturating_to_fixed<F: Fixed>(self) -> F {
416 let kind =
417 float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
418 let helper = FromFloatHelper { kind };
419 F::saturating_from_float_helper(Private, helper)
420 }
421 }
422
423 comment! {
424 "Converts a floating-point number to a fixed-point
425number, wrapping if it does not fit.
426
427Rounding is to the nearest, with ties rounded to even.
428
429# Panics
430
431Panics if `self` is not [finite].
432
433[finite]: ", $link, "::is_finite
434";
435 #[inline]
436 #[track_caller]
437 fn wrapping_to_fixed<F: Fixed>(self) -> F {
438 let (wrapped, _) = ToFixed::overflowing_to_fixed(self);
439 wrapped
440 }
441 }
442
443 comment! {
444 "Converts a floating-point number to a fixed-point number.
445
446Returns a [tuple] of the fixed-point number and a [`bool`] indicating
447whether an overflow has occurred. On overflow, the wrapped value is
448returned.
449
450Rounding is to the nearest, with ties rounded to even.
451
452# Panics
453
454Panics if `self` is not [finite].
455
456[finite]: ", $link, "::is_finite
457";
458 #[inline]
459 #[track_caller]
460 fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
461 let kind =
462 float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
463 let helper = FromFloatHelper { kind };
464 F::overflowing_from_float_helper(Private, helper)
465 }
466 }
467
468 comment! {
469 "Converts a floating-point number to a fixed-point
470number, panicking if it does not fit.
471
472Rounding is to the nearest, with ties rounded to even.
473
474# Panics
475
476Panics if `self` is not [finite] or if the value does not fit, even
477when debug assertions are not enabled.
478
479[finite]: ", $link, "::is_finite
480";
481 #[inline]
482 #[track_caller]
483 fn unwrapped_to_fixed<F: Fixed>(self) -> F {
484 match ToFixed::overflowing_to_fixed(self) {
485 (val, false) => val,
486 (_, true) => panic!("overflow"),
487 }
488 }
489 }
490 }
491 };
492}
493
494#[cfg(feature = "nightly-float")]
495impl_float! { f16, "f16", "f16::from_bits(0x{:04X}) overflows", |x: f16| x.to_bits() }
496impl_float! { half_f16, "half::f16", "{} overflows", |x| x }
497impl_float! { half_bf16, "half::bf16", "{} overflows", |x| x }
498impl_float! { f32, "f32", "{} overflows", |x| x }
499impl_float! { f64, "f64", "{} overflows", |x| x }
500#[cfg(feature = "nightly-float")]
501impl_float! { f128, "f128", "f128::from_bits(0x{:032X}) overflows", |x: f128| x.to_bits() }
502impl_float! { F128, "F128", "F128::from_bits(0x{:032X}) overflows", |x: F128| x.to_bits() }
503impl_float! { F128Bits, "f64", "F128Bits({}) overflows", |x: F128Bits| x.0 }