1use core::{fmt, fmt::Write, num::Wrapping};
2
3use crate::{AsFloat, Q};
4
5macro_rules! impl_fmt {
11 ($tr:path) => {
12 impl<T, A, const F: i8> $tr for Q<T, A, F>
13 where
14 T: AsFloat,
15 {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 <f64 as $tr>::fmt(&self.as_f64(), f)
18 }
19 }
20 };
21}
22impl_fmt!(fmt::Display);
23impl_fmt!(fmt::UpperExp);
24impl_fmt!(fmt::LowerExp);
25
26#[cfg(feature = "defmt")]
27impl<T, A, const F: i8> defmt::Format for Q<T, A, F>
28where
29 T: AsFloat,
30{
31 fn format(&self, fmt: defmt::Formatter<'_>) {
32 defmt::write!(fmt, "{=f32}", self.as_f32());
33 }
34}
35
36impl<T, A, const F: i8> fmt::Debug for Q<T, A, F>
47where
48 T: fmt::Debug,
49{
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 self.inner.fmt(f)
52 }
53}
54
55trait RadixValue: Copy {
56 fn is_negative(self) -> bool;
57 fn magnitude(self) -> u64;
58}
59
60macro_rules! impl_unsigned_radix_value {
61 ($($ty:ty),* $(,)?) => {
62 $(
63 impl RadixValue for $ty {
64 #[inline]
65 fn is_negative(self) -> bool {
66 false
67 }
68
69 #[inline]
70 fn magnitude(self) -> u64 {
71 self as _
72 }
73 }
74 )*
75 };
76}
77
78macro_rules! impl_signed_radix_value {
79 ($($ty:ty),* $(,)?) => {
80 $(
81 impl RadixValue for $ty {
82 #[inline]
83 fn is_negative(self) -> bool {
84 self.is_negative()
85 }
86
87 #[inline]
88 fn magnitude(self) -> u64 {
89 self.unsigned_abs() as _
90 }
91 }
92 )*
93 };
94}
95
96macro_rules! impl_wrapping_unsigned_radix_value {
97 ($($ty:ty),* $(,)?) => {
98 $(
99 impl RadixValue for Wrapping<$ty> {
100 #[inline]
101 fn is_negative(self) -> bool {
102 false
103 }
104
105 #[inline]
106 fn magnitude(self) -> u64 {
107 self.0 as _
108 }
109 }
110 )*
111 };
112}
113
114macro_rules! impl_wrapping_signed_radix_value {
115 ($($ty:ty),* $(,)?) => {
116 $(
117 impl RadixValue for Wrapping<$ty> {
118 #[inline]
119 fn is_negative(self) -> bool {
120 self.0.is_negative()
121 }
122
123 #[inline]
124 fn magnitude(self) -> u64 {
125 self.0.unsigned_abs() as _
126 }
127 }
128 )*
129 };
130}
131
132impl_unsigned_radix_value!(u8, u16, u32, u64);
133impl_signed_radix_value!(i8, i16, i32, i64);
134impl_wrapping_unsigned_radix_value!(u8, u16, u32, u64);
135impl_wrapping_signed_radix_value!(i8, i16, i32, i64);
136
137#[derive(Copy, Clone)]
138struct Radix {
139 bits: u8,
140 table: &'static str,
141}
142
143impl Radix {
144 #[inline]
145 const fn mask(self) -> u8 {
146 (1u8 << self.bits) - 1
147 }
148
149 #[inline]
150 const fn ceil_digits(self, bits: usize) -> usize {
151 bits.div_ceil(self.bits as _)
152 }
153
154 #[inline]
155 const fn div_mod(self, bits: i8) -> (usize, u8) {
156 let bits = bits.unsigned_abs();
157 ((bits / self.bits) as usize, bits % self.bits)
158 }
159
160 #[inline]
161 const fn shifted_digit(self, magnitude: u64, shift: u8, index: usize) -> char {
162 let mask = self.mask();
163 let offset = index * self.bits as usize;
164 let value = if let Some(right) = offset.checked_sub(shift as usize) {
165 if right >= u64::BITS as usize {
166 0
167 } else {
168 ((magnitude >> right) & mask as u64) as u8
169 }
170 } else {
171 ((magnitude << (shift as usize - offset)) & mask as u64) as u8
172 };
173 self.table.as_bytes()[2 + value as usize] as char
174 }
175
176 fn format_fixed(
177 self,
178 negative: bool,
179 magnitude: u64,
180 frac_bits: i8,
181 f: &mut fmt::Formatter<'_>,
182 ) -> fmt::Result {
183 let magnitude_bits = (u64::BITS - magnitude.leading_zeros()) as usize;
184 let body_len = if frac_bits > 0 {
185 let frac_bits = frac_bits as usize;
186 let frac_digits = self.ceil_digits(frac_bits);
187 let effective_digits = if magnitude == 0 {
188 0
189 } else {
190 self.ceil_digits(magnitude_bits + frac_digits * (self.bits - 1) as usize)
191 };
192 effective_digits.saturating_sub(frac_digits).max(1) + 1 + frac_digits
193 } else {
194 let (zero_digits, shift) = self.div_mod(frac_bits);
195 if magnitude == 0 {
196 2
197 } else {
198 self.ceil_digits(magnitude_bits + shift as usize) + zero_digits + 1
199 }
200 };
201 let sign = if negative {
202 "-"
203 } else if f.sign_plus() {
204 "+"
205 } else {
206 ""
207 };
208 let prefix = if f.alternate() { &self.table[..2] } else { "" };
209 let total_len = sign.len() + prefix.len() + body_len;
210 let pad_len = f.width().unwrap_or_default().saturating_sub(total_len);
211 let zero_pad = if f.sign_aware_zero_pad() && f.align().is_none() {
212 pad_len
213 } else {
214 0
215 };
216 let align = f.align().unwrap_or(fmt::Alignment::Right);
217 let (left_pad, right_pad) = if zero_pad != 0 {
218 (0, 0)
219 } else {
220 match align {
221 fmt::Alignment::Left => (0, pad_len),
222 fmt::Alignment::Center => (pad_len / 2, pad_len - pad_len / 2),
223 fmt::Alignment::Right => (pad_len, 0),
224 }
225 };
226
227 for _ in 0..left_pad {
228 f.write_char(f.fill())?;
229 }
230 f.write_str(sign)?;
231 f.write_str(prefix)?;
232 for _ in 0..zero_pad {
233 f.write_char('0')?;
234 }
235
236 if frac_bits > 0 {
237 let frac_bits = frac_bits as usize;
238 let frac_digits = self.ceil_digits(frac_bits);
239 let shift = (frac_digits * self.bits as usize - frac_bits) as u8;
240 let effective_digits = if magnitude == 0 {
241 0
242 } else {
243 self.ceil_digits(magnitude_bits + shift as usize)
244 };
245 if effective_digits <= frac_digits {
246 f.write_char('0')?;
247 } else {
248 for index in (frac_digits..effective_digits).rev() {
249 f.write_char(self.shifted_digit(magnitude, shift, index))?;
250 }
251 }
252 f.write_char('.')?;
253 for index in (0..frac_digits).rev() {
254 f.write_char(self.shifted_digit(magnitude, shift, index))?;
255 }
256 } else {
257 let (zero_digits, shift) = self.div_mod(frac_bits);
258 if magnitude == 0 {
259 f.write_char('0')?;
260 } else {
261 let digits = self.ceil_digits(magnitude_bits + shift as usize);
262 for index in (0..digits).rev() {
263 f.write_char(self.shifted_digit(magnitude, shift, index))?;
264 }
265 for _ in 0..zero_digits {
266 f.write_char('0')?;
267 }
268 }
269 f.write_char('.')?;
270 }
271
272 for _ in 0..right_pad {
273 f.write_char(f.fill())?;
274 }
275 Ok(())
276 }
277}
278
279macro_rules! impl_radix_fmt {
280 ($tr:path, $radix:expr) => {
281 impl<T: RadixValue, A, const F: i8> $tr for Q<T, A, F> {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 const {
284 assert!(
285 F != i8::MIN,
286 "fractional bit count must not be i8::MIN for formatting"
287 );
288 }
289 $radix.format_fixed(self.inner.is_negative(), self.inner.magnitude(), F, f)
290 }
291 }
292 };
293}
294
295const BINARY: Radix = Radix {
296 bits: 1,
297 table: "0b01",
298};
299const OCTAL: Radix = Radix {
300 bits: 3,
301 table: "0o01234567",
302};
303const LOWER_HEX: Radix = Radix {
304 bits: 4,
305 table: "0x0123456789abcdef",
306};
307const UPPER_HEX: Radix = Radix {
308 bits: 4,
309 table: "0X0123456789ABCDEF",
310};
311
312impl_radix_fmt!(fmt::Binary, BINARY);
313impl_radix_fmt!(fmt::Octal, OCTAL);
314impl_radix_fmt!(fmt::LowerHex, LOWER_HEX);
315impl_radix_fmt!(fmt::UpperHex, UPPER_HEX);
316
317#[cfg(test)]
318mod test {
319 #[cfg(feature = "defmt")]
320 #[test]
321 fn defmt_format_impls_exist() {
322 fn assert_defmt<T: defmt::Format>() {}
323
324 assert_defmt::<crate::Q8<4>>();
325 assert_defmt::<crate::P8<4>>();
326 assert_defmt::<crate::W8<4>>();
327 assert_defmt::<crate::V8<4>>();
328 }
329
330 #[cfg(feature = "std")]
331 #[test]
332 fn display() {
333 use crate::Q32;
334 use std::format;
335
336 assert_eq!(format!("{}", Q32::<9>::new(0x12345)), "145.634765625");
337 assert_eq!(format!("{}", Q32::<9>::from_int(99)), "99");
338 }
339
340 #[cfg(feature = "std")]
341 #[test]
342 fn float_accessors_cover_wrapping_types() {
343 use crate::{V8, W8};
344 use core::num::Wrapping;
345
346 assert_eq!(W8::<4>::new(Wrapping(-4)).as_f32(), -0.25);
347 assert_eq!(V8::<4>::new(Wrapping(4)).as_f64(), 0.25);
348 }
349
350 #[cfg(feature = "std")]
351 #[test]
352 fn radix_dot_examples() {
353 use crate::Q8;
354 use std::format;
355
356 assert_eq!(format!("{:#b}", Q8::<3>::new(0b01101001)), "0b1101.001");
357 assert_eq!(format!("{:x}", Q8::<3>::new(0b01101001)), "d.2");
358 assert_eq!(format!("{:o}", Q8::<5>::new(1)), "0.02");
359 assert_eq!(format!("{:x}", Q8::<-2>::new(3)), "c.");
360 }
361
362 #[cfg(feature = "std")]
363 #[test]
364 fn radix_dot_leading_zero_and_zero_value() {
365 use crate::Q8;
366 use std::format;
367
368 assert_eq!(format!("{:b}", Q8::<3>::new(1)), "0.001");
369 assert_eq!(format!("{:x}", Q8::<7>::new(1)), "0.02");
370 assert_eq!(format!("{:#x}", Q8::<7>::new(1)), "0x0.02");
371 assert_eq!(format!("{:b}", Q8::<5>::new(0)), "0.00000");
372 assert_eq!(format!("{:x}", Q8::<-5>::new(0)), "0.");
373 }
374
375 #[cfg(feature = "std")]
376 #[test]
377 fn radix_dot_signed_values_are_magnitude_based() {
378 use crate::{Q8, W8};
379 use core::num::Wrapping;
380 use std::format;
381
382 assert_eq!(format!("{:b}", Q8::<3>::new(-0x14)), "-10.100");
383 assert_eq!(format!("{:#x}", Q8::<4>::new(-0x14)), "-0x1.4");
384 assert_eq!(format!("{:o}", Q8::<0>::new(-1)), "-1.");
385 assert_eq!(format!("{:x}", Q8::<4>::new(i8::MIN)), "-8.0");
386 assert_eq!(format!("{:#b}", W8::<3>::new(Wrapping(-0x14))), "-0b10.100");
387 }
388
389 #[cfg(feature = "std")]
390 #[test]
391 fn radix_dot_unsigned_and_wrapping_unsigned() {
392 use crate::{P8, V8};
393 use core::num::Wrapping;
394 use std::format;
395
396 assert_eq!(format!("{:x}", P8::<4>::new(u8::MAX)), "f.f");
397 assert_eq!(
398 format!("{:b}", V8::<3>::new(Wrapping(0b1111_1111))),
399 "11111.111"
400 );
401 }
402
403 #[cfg(feature = "std")]
404 #[test]
405 fn radix_dot_handles_large_positive_and_negative_f() {
406 use crate::{Q8, Q64};
407 use std::format;
408
409 assert_eq!(format!("{:b}", Q8::<7>::new(i8::MAX)), "0.1111111");
410 assert_eq!(format!("{:b}", Q8::<-7>::new(1)), "10000000.");
411 assert_eq!(
412 format!("{:x}", Q64::<63>::new(i64::MAX)),
413 "0.fffffffffffffffe"
414 );
415 assert_eq!(format!("{:x}", Q64::<-63>::new(1)), "8000000000000000.");
416 assert_eq!(
417 format!("{:b}", Q64::<-63>::new(1)),
418 "1\
419000000000000000000000000000000000000000000000000000000000000000."
420 );
421 }
422
423 #[cfg(feature = "std")]
424 #[test]
425 fn radix_dot_handles_zero_fractional_bits() {
426 use crate::Q8;
427 use std::format;
428
429 assert_eq!(format!("{:b}", Q8::<0>::new(0b1010)), "1010.");
430 assert_eq!(format!("{:#x}", Q8::<0>::new(0x2a)), "0x2a.");
431 }
432
433 #[cfg(feature = "std")]
434 #[test]
435 fn radix_dot_respects_width_alignment_and_zero_fill() {
436 use crate::Q8;
437 use std::format;
438
439 assert_eq!(format!("{:>10x}", Q8::<4>::new(0x14)), " 1.4");
440 assert_eq!(format!("{:#010x}", Q8::<4>::new(0x14)), "0x000001.4");
441 assert_eq!(format!("{:#010x}", Q8::<4>::new(-0x14)), "-0x00001.4");
442 assert_eq!(format!("{:<010x}", Q8::<4>::new(0x14)), "1.4 ");
443 assert_eq!(format!("{:^010x}", Q8::<4>::new(0x14)), " 1.4 ");
444 }
445
446 #[cfg(feature = "std")]
447 #[test]
448 fn debug_stays_raw() {
449 use crate::Q8;
450 use std::format;
451
452 assert_eq!(format!("{:?}", Q8::<3>::new(-0x14)), "-20");
453 assert_eq!(format!("{:b}", Q8::<3>::new(-0x14)), "-10.100");
454 }
455}