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