From 2d5a5c3adad32bc00c7c9600568defd034f6727d Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Mon, 19 Aug 2019 18:49:40 +0200 Subject: [PATCH] properly handle precision in Binary, Octal, LowerHex, UpperHex --- src/consts.rs | 2 +- src/display.rs | 326 ++++++++++++++++++++++++++++++---------------- src/int_helper.rs | 12 ++ 3 files changed, 224 insertions(+), 116 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index dd88483..e186368 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -25,7 +25,7 @@ number, and are rounded down at that precision. use fixed::{consts, types::I8F8}; let tau = I8F8::from_num(consts::TAU); println!("τ = 2π with eight binary places is {:b}", tau); -assert_eq!(format!("{:b}", tau), "110.01001000"); +assert_eq!(format!("{:.8b}", tau), "110.01001000"); ``` */ diff --git a/src/display.rs b/src/display.rs index d874a97..3f85e3a 100644 --- a/src/display.rs +++ b/src/display.rs @@ -14,6 +14,7 @@ // . use crate::{ + frac::False, helpers::IntHelper, traits::Fixed, types::{LeEqU128, LeEqU16, LeEqU32, LeEqU64, LeEqU8}, @@ -21,124 +22,182 @@ use crate::{ FixedU8, }; use core::{ - cmp::Ordering, - fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, + cmp::{self, Ordering}, + fmt::{ + Alignment, Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, + UpperHex, + }, mem, str, }; -trait Radix2 { - fn digit_bits(&self) -> u32; - fn prefix(&self) -> &'static str; - fn encode_digits(&self, digits: &mut [u8]); +fn pad( + is_neg: bool, + maybe_prefix: &str, + abs: &str, + end_zeros: usize, + fmt: &mut Formatter, +) -> FmtResult { + use core::fmt::Write; + let sign = if is_neg { + "-" + } else if fmt.sign_plus() { + "+" + } else { + "" + }; + let prefix = if fmt.alternate() { maybe_prefix } else { "" }; + let req_width = sign.len() + prefix.len() + abs.len() + end_zeros; + let pad = fmt + .width() + .and_then(|w| w.checked_sub(req_width)) + .unwrap_or(0); + let (pad_left, pad_zeros, pad_right) = if fmt.sign_aware_zero_pad() { + (0, pad, 0) + } else { + match fmt.align() { + Some(Alignment::Left) => (0, 0, pad), + Some(Alignment::Center) => (pad / 2, 0, pad - pad / 2), + None | Some(Alignment::Right) => (pad, 0, 0), + } + }; + let fill = fmt.fill(); + for _ in 0..pad_left { + fmt.write_char(fill)?; + } + fmt.write_str(sign)?; + fmt.write_str(prefix)?; + for _ in 0..pad_zeros { + fmt.write_char('0')?; + } + fmt.write_str(abs)?; + for _ in 0..end_zeros { + fmt.write_char('0')?; + } + for _ in 0..pad_right { + fmt.write_char(fill)?; + } + Ok(()) } -macro_rules! radix2 { - ($Radix:ident($bits:expr, $prefix:expr), $($range:pat => $inc:expr),+) => { - #[derive(Clone, Copy)] - struct $Radix; - impl Radix2 for $Radix { - #[inline] - fn digit_bits(&self) -> u32 { - $bits - } - - #[inline] - fn prefix(&self) -> &'static str { - $prefix - } - - #[inline] - fn encode_digits(&self, digits: &mut [u8]) { - for digit in digits.iter_mut() { - *digit += match *digit { - $($range => $inc,)+ - _ => continue, - }; +#[derive(Clone, Copy)] +enum Radix2 { + Bin, + Oct, + LowHex, + UpHex, +} +impl Radix2 { + #[inline] + fn digit_bits(self) -> u32 { + match self { + Radix2::Bin => 1, + Radix2::Oct => 3, + Radix2::LowHex => 4, + Radix2::UpHex => 4, + } + } + #[inline] + fn prefix(self) -> &'static str { + match self { + Radix2::Bin => "0b", + Radix2::Oct => "0o", + Radix2::LowHex => "0x", + Radix2::UpHex => "0x", + } + } + #[inline] + fn encode_digits(self, digits: &mut [u8]) { + for digit in digits.iter_mut() { + if *digit < 10 { + *digit += b'0'; + } else if *digit < 16 { + match self { + Radix2::LowHex => *digit += b'a' - 10, + _ => *digit += b'A' - 10, } } } - }; + } } -radix2! { Bin(1, "0b"), 0..=1 => b'0' } -radix2! { Oct(3, "0o"), 0..=7 => b'0' } -radix2! { LowHex(4, "0x"), 0..=9 => b'0', 10..=15 => b'a' - 10 } -radix2! { UpHex(4, "0x"), 0..=9 => b'0', 10..=15 => b'A' - 10 } - -trait FmtRadix2Helper: IntHelper { - fn take_int_digit(&mut self, digit_bits: u32) -> u8; - fn take_frac_digit(&mut self, digit_bits: u32) -> u8; -} - -macro_rules! fmt_radix2_helper { - ($($UInner:ty)*) => { $( - impl FmtRadix2Helper for $UInner { - #[inline] - fn take_int_digit(&mut self, digit_bits: u32) -> u8 { - let mask = (1 << digit_bits) - 1; - let ret = (*self & mask) as u8; - *self >>= digit_bits; - ret - } - - #[inline] - fn take_frac_digit(&mut self, digit_bits: u32) -> u8 { - let nbits = <$UInner>::NBITS; - let rem_bits = nbits - digit_bits; - let mask = !0 << rem_bits; - let ret = ((*self & mask) >> rem_bits) as u8; - *self <<= digit_bits; - ret - } - } - )* }; -} - -fmt_radix2_helper! { u8 u16 u32 u64 u128 } - -fn fmt_radix2_helper( - frac_bits: u32, - (is_neg, mut int, mut frac): (bool, F, F), - radix: &dyn Radix2, +fn fmt_radix2_helper( + (is_neg, mut int, mut frac): (bool, I, I), + radix: Radix2, fmt: &mut Formatter, -) -> FmtResult { - let int_bits = F::NBITS - frac_bits; - let digit_bits: u32 = radix.digit_bits(); - // 128 binary digits, one radix point, one leading zero - let mut buf: [u8; 130] = [0; 130]; - let max_int_digits = (int_bits + digit_bits - 1) / digit_bits; - let frac_digits = (frac_bits + digit_bits - 1) / digit_bits; - let (mut int_start, frac_start); - if max_int_digits == 0 { - buf[0] = b'0'; - buf[1] = b'.'; - int_start = 0; - frac_start = 2; +) -> FmtResult +where + I: IntHelper, +{ + let digit_bits = radix.digit_bits(); + let compl_digit_bits = I::NBITS - digit_bits; + let int_digit_mask = !(!I::ZERO << digit_bits); + // 128 binary digits, one radix point, one leading zero. + // The leading zero has two purposes: + // 1. If there are no integer bits, we still want to print "0." instead of ".". + // 2. If rounding causes a carry, we can carry into this extra zero. + let mut buf = [0u8; 130]; + + // buf[1..int_digits + 1] => significant integer digits (could be b"") + let int_digits = ((I::NBITS - int.leading_zeros() + digit_bits - 1) / digit_bits) as usize; + for b in buf[1..int_digits + 1].iter_mut().rev() { + *b = (int & int_digit_mask).lower_byte(); + int = int >> digit_bits; + } + + // buf[int_digits + 1..int_digits + 2] => b"." + buf[int_digits + 1] = b'.'; + + // buf[int_digits + 2..int_digits + frac_digits + 2] => fractional digits + let mut frac_digits = + ((I::NBITS - frac.trailing_zeros() + digit_bits - 1) / digit_bits) as usize; + if let Some(precision) = fmt.precision() { + frac_digits = cmp::min(frac_digits, precision); + } + for (i, b) in buf[int_digits + 2..int_digits + frac_digits + 2] + .iter_mut() + .enumerate() + { + *b = (frac >> compl_digit_bits).lower_byte(); + frac = frac << digit_bits; + if frac == I::ZERO { + frac_digits = i + 1; + break; + } + } + let least_significant_index = if frac_digits > 0 { + int_digits + frac_digits + 1 } else { - int_start = max_int_digits; - for r in buf[0..max_int_digits as usize].iter_mut().rev() { - *r = int.take_int_digit(digit_bits); - int_start -= 1; - if int == F::ZERO { + int_digits + }; + let round_up = frac > I::MSB || (frac == I::MSB && buf[least_significant_index].is_odd()); + if round_up { + for b in buf[0..int_digits + frac_digits + 2].iter_mut().rev() { + if *b < int_digit_mask.lower_byte() { + *b += 1; break; } + if *b == b'.' { + continue; + } + *b = 0; + if frac_digits > 0 { + frac_digits -= 1; + } } - buf[max_int_digits as usize] = b'.'; - frac_start = max_int_digits + 1; } - let end; - if frac_digits == 0 { - end = frac_start - 1; + let end_zeros = fmt.precision().map(|x| x - frac_digits).unwrap_or(0); + let begin = if buf[0] > 0 || buf[1] == b'.' { 0 } else { 1 }; + let end = if frac_digits > 0 { + int_digits + frac_digits + 2 + } else if end_zeros > 0 { + int_digits + 2 } else { - end = frac_start + frac_digits; - for r in buf[frac_start as usize..end as usize].iter_mut() { - *r = frac.take_frac_digit(digit_bits); - } - } - let used_buf = &mut buf[int_start as usize..end as usize]; + int_digits + 1 + }; + let used_buf = &mut buf[begin..end]; radix.encode_digits(used_buf); let str_buf = str::from_utf8(used_buf).unwrap(); - fmt.pad_integral(!is_neg, radix.prefix(), str_buf) + pad(is_neg, radix.prefix(), str_buf, end_zeros, fmt) } #[inline] @@ -164,12 +223,12 @@ where } #[inline] -fn fmt_radix2(num: F, radix: &dyn Radix2, fmt: &mut Formatter) -> FmtResult +fn fmt_radix2(num: F, radix: Radix2, fmt: &mut Formatter) -> FmtResult where F::Bits: IntHelper, - ::Unsigned: FmtRadix2Helper, + ::Unsigned: IntHelper, { - fmt_radix2_helper(F::frac_nbits(), parts(num), radix, fmt) + fmt_radix2_helper(parts(num), radix, fmt) } macro_rules! impl_fmt { @@ -188,25 +247,25 @@ macro_rules! impl_fmt { impl Binary for $Fixed { fn fmt(&self, f: &mut Formatter) -> FmtResult { - fmt_radix2(*self, &Bin, f) + fmt_radix2(*self, Radix2::Bin, f) } } impl Octal for $Fixed { fn fmt(&self, f: &mut Formatter) -> FmtResult { - fmt_radix2(*self, &Oct, f) + fmt_radix2(*self, Radix2::Oct, f) } } impl LowerHex for $Fixed { fn fmt(&self, f: &mut Formatter) -> FmtResult { - fmt_radix2(*self, &LowHex, f) + fmt_radix2(*self, Radix2::LowHex, f) } } impl UpperHex for $Fixed { fn fmt(&self, f: &mut Formatter) -> FmtResult { - fmt_radix2(*self, &UpHex, f) + fmt_radix2(*self, Radix2::UpHex, f) } } }; @@ -409,9 +468,10 @@ where #[cfg(test)] mod tests { use crate::{frac::Unsigned, *}; - use core::{ + use std::{ fmt::{Debug, Error as FmtError, Formatter, Result as FmtResult, Write}, - mem, + format, mem, + string::String, }; struct Buf([u8; 256]); @@ -474,20 +534,56 @@ mod tests { }}; } + fn trim_frac_zeros(mut x: &str) -> &str { + while x.ends_with('0') { + x = &x[..x.len() - 1]; + } + if x.ends_with('.') { + x = &x[..x.len() - 1]; + } + x + } + + fn up_frac_digits(x: &mut String, frac_digits: usize) { + match x.find('.') { + Some(point) => match frac_digits.checked_sub(x.len() - point - 1) { + Some(additional) => { + x.reserve(additional); + for _ in 0..additional { + x.push('0'); + } + } + None => {} + }, + None => { + x.reserve(frac_digits + 1); + x.push('.'); + for _ in 0..frac_digits { + x.push('0'); + } + } + } + } + #[test] fn hex() { use crate::frac::U7 as Frac; let frac = Frac::U32; - for i in 0..(1 << frac) { - let p = 0x1234_5678_9abc_def0u64 ^ i as u64; + for i in 0..(1u32 << frac) { + let p = 0x1234_5678_9abc_def0u64 ^ u64::from(i); let n = -0x1234_5678_9abc_def0i64 ^ i64::from(i); let f_p = FixedU64::::from_bits(p); let f_n = FixedI64::::from_bits(n); - assert_eq_fmt!(("{:x}", f_p), ("{:x}.{:02x}", p >> frac, (p & 0x7f) << 1)); - assert_eq_fmt!( - ("{:x}", f_n), - ("-{:x}.{:02x}", n.abs() >> frac, (n.abs() & 0x7f) << 1) - ); + let mut check_p = format!("{:x}.{:02x}", p >> frac, (p & 0x7f) << 1); + up_frac_digits(&mut check_p, 1000); + let trimmed_p = trim_frac_zeros(&check_p); + let mut check_n = format!("-{:x}.{:02x}", n.abs() >> frac, (n.abs() & 0x7f) << 1); + up_frac_digits(&mut check_n, 1000); + let trimmed_n = trim_frac_zeros(&check_n); + assert_eq!(format!("{:.1000x}", f_p), check_p); + assert_eq!(format!("{:x}", f_p), trimmed_p); + assert_eq!(format!("{:.1000x}", f_n), check_n); + assert_eq!(format!("{:x}", f_n), trimmed_n); } } diff --git a/src/int_helper.rs b/src/int_helper.rs index f8c87a6..3ca09e7 100644 --- a/src/int_helper.rs +++ b/src/int_helper.rs @@ -49,6 +49,8 @@ where fn overflowing_add(self, val: Self) -> (Self, bool); fn overflowing_mul(self, val: Self) -> (Self, bool); fn leading_zeros(self) -> u32; + fn trailing_zeros(self) -> u32; + fn lower_byte(self) -> u8; fn to_fixed_helper( self, @@ -100,6 +102,16 @@ macro_rules! sealed_int { self.leading_zeros() } + #[inline] + fn trailing_zeros(self) -> u32 { + self.trailing_zeros() + } + + #[inline] + fn lower_byte(self) -> u8 { + self as u8 + } + #[inline] fn to_repr_fixed(self) -> Self::ReprFixed { Self::ReprFixed::from_bits(self.int_repr())