Skip to content

Commit cfae12e

Browse files
authored
build_const-friendly fmt::Debug and human-readable decimal fmt::Display (#211)
* build_const-friendly fmt::Debug for fixed-point shows the raw hex, type, and fractional bits, in a manner that also compiles as code. * human-readable fmt::Display for fixed-point * add unit test for fixed-point fmt::Display * fix leading spaces and negatives * handle negative numbers greater than -1 --------- Co-authored-by: lifning <>
1 parent 6a3fcc1 commit cfae12e

File tree

1 file changed

+116
-17
lines changed

1 file changed

+116
-17
lines changed

src/fixed.rs

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,24 @@ macro_rules! impl_common_fixed_ops {
241241
Self(self.0 >> rhs)
242242
}
243243
}
244+
245+
impl<const B: u32> core::fmt::Debug for Fixed<$t, B> {
246+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
247+
let raw: $t = self.to_bits();
248+
write!(
249+
f,
250+
concat!(
251+
"Fixed::<",
252+
stringify!($t),
253+
",{}>::from_bits({:#x}_u32 as ",
254+
stringify!($t),
255+
")"
256+
),
257+
B, raw
258+
)
259+
}
260+
}
261+
244262
impl_trait_op_unit!($t, Not, not);
245263
impl_trait_op_self_rhs!($t, Add, add);
246264
impl_trait_op_self_rhs!($t, Sub, sub);
@@ -335,18 +353,11 @@ macro_rules! impl_signed_fixed_ops {
335353
}
336354
}
337355
impl_trait_op_unit!($t, Neg, neg);
338-
impl<const B: u32> core::fmt::Debug for Fixed<$t, B> {
339-
#[inline]
356+
357+
impl<const B: u32> core::fmt::Display for Fixed<$t, B> {
340358
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
341-
let whole: $t = self.trunc().to_bits() >> B;
342-
let fract: $t = self.fract().to_bits();
343-
let divisor: $t = 1 << B;
344-
if self.is_negative() {
345-
let whole = whole.unsigned_abs();
346-
write!(f, "-({whole}+{fract}/{divisor})")
347-
} else {
348-
write!(f, "{whole}+{fract}/{divisor}")
349-
}
359+
let neg = self.to_bits() < 0;
360+
fixed_fmt_abs::<B>(f, self.to_bits().abs() as u32, neg)
350361
}
351362
}
352363
};
@@ -393,17 +404,105 @@ macro_rules! impl_unsigned_fixed_ops {
393404
Self(self.0 & (<$t>::MAX << B))
394405
}
395406
}
396-
impl<const B: u32> core::fmt::Debug for Fixed<$t, B> {
397-
#[inline]
407+
408+
impl<const B: u32> core::fmt::Display for Fixed<$t, B> {
398409
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
399-
let whole: $t = self.trunc().to_bits() >> B;
400-
let fract: $t = self.fract().to_bits();
401-
let divisor: $t = 1 << B;
402-
write!(f, "{whole}+{fract}/{divisor}")
410+
fixed_fmt_abs::<B>(f, self.to_bits() as u32, false)
403411
}
404412
}
405413
};
406414
}
407415
impl_unsigned_fixed_ops!(u8);
408416
impl_unsigned_fixed_ops!(u16);
409417
impl_unsigned_fixed_ops!(u32);
418+
419+
fn fixed_fmt_abs<const B: u32>(
420+
f: &mut core::fmt::Formatter, abs: u32, neg: bool,
421+
) -> core::fmt::Result {
422+
let precision = f.precision().unwrap_or(const { ((B as usize) + 1) / 3 });
423+
let width = f.width().unwrap_or(0).saturating_sub(precision + 1);
424+
let fract = abs & ((1 << B) - 1);
425+
let fract_dec = 10u32
426+
.checked_pow(precision as u32)
427+
.and_then(|digits| fract.checked_mul(digits))
428+
.map(|x| (x >> B) as u64)
429+
.unwrap_or_else(|| (fract as u64 * 10u64.pow(precision as u32) >> B));
430+
let mut ones = (abs >> B) as i32;
431+
if neg {
432+
if ones != 0 {
433+
ones = ones.neg()
434+
} else {
435+
let width = width.saturating_sub(2);
436+
return write!(f, "{:width$}-0.{fract_dec:0precision$}", "");
437+
}
438+
}
439+
write!(f, "{ones:width$}.{fract_dec:0precision$}")
440+
}
441+
442+
#[cfg(test)]
443+
mod test {
444+
use crate::fixed::{i16fx14, i32fx8};
445+
use core::{fmt::Write, str};
446+
447+
struct WriteBuf<const N: usize>([u8; N], usize);
448+
impl<'a, const N: usize> Default for WriteBuf<N> {
449+
fn default() -> Self {
450+
Self([0u8; N], 0)
451+
}
452+
}
453+
impl<const N: usize> Write for WriteBuf<N> {
454+
fn write_str(&mut self, s: &str) -> core::fmt::Result {
455+
let src = s.as_bytes();
456+
let len = (self.0.len() - self.1).min(src.len());
457+
self.0[self.1..self.1 + len].copy_from_slice(&src[..len]);
458+
self.1 += len;
459+
if len < src.len() {
460+
Err(core::fmt::Error)
461+
} else {
462+
Ok(())
463+
}
464+
}
465+
}
466+
impl<const N: usize> WriteBuf<N> {
467+
fn take(&mut self) -> &str {
468+
let len = self.1;
469+
self.1 = 0;
470+
str::from_utf8(&self.0[..len]).unwrap()
471+
}
472+
}
473+
474+
#[test_case]
475+
fn decimal_display() {
476+
let mut wbuf = WriteBuf::<16>::default();
477+
478+
let x = i32fx8::from_bits(0x12345678);
479+
480+
write!(&mut wbuf, "{x}").unwrap();
481+
assert_eq!(wbuf.take(), "1193046.468");
482+
483+
write!(&mut wbuf, "{x:11.1}").unwrap();
484+
assert_eq!(wbuf.take(), " 1193046.4");
485+
486+
write!(&mut wbuf, "{x:1.6}").unwrap();
487+
assert_eq!(wbuf.take(), "1193046.468750");
488+
489+
let x = x.neg();
490+
write!(&mut wbuf, "{x}").unwrap();
491+
assert_eq!(wbuf.take(), "-1193046.468");
492+
493+
let x = i16fx14::from_bits(0x6544 as i16);
494+
write!(&mut wbuf, "{x}").unwrap();
495+
assert_eq!(wbuf.take(), "1.58227");
496+
497+
let x = x.neg();
498+
write!(&mut wbuf, "{x:.10}").unwrap();
499+
assert_eq!(wbuf.take(), "-1.5822753906");
500+
501+
write!(&mut wbuf, "{x:9.1}").unwrap();
502+
assert_eq!(wbuf.take(), " -1.5");
503+
504+
let x = x.add(i16fx14::wrapping_from(1));
505+
write!(&mut wbuf, "{x:9.2}").unwrap();
506+
assert_eq!(wbuf.take(), " -0.58");
507+
}
508+
}

0 commit comments

Comments
 (0)