properly handle precision in Binary, Octal, LowerHex, UpperHex

This commit is contained in:
Trevor Spiteri 2019-08-19 18:49:40 +02:00
parent 76a0abe468
commit 2d5a5c3ada
3 changed files with 224 additions and 116 deletions

View File

@ -25,7 +25,7 @@ number, and are rounded down at that precision.
use fixed::{consts, types::I8F8}; use fixed::{consts, types::I8F8};
let tau = I8F8::from_num(consts::TAU); let tau = I8F8::from_num(consts::TAU);
println!("τ = 2π with eight binary places is {:b}", tau); println!("τ = 2π with eight binary places is {:b}", tau);
assert_eq!(format!("{:b}", tau), "110.01001000"); assert_eq!(format!("{:.8b}", tau), "110.01001000");
``` ```
*/ */

View File

@ -14,6 +14,7 @@
// <https://opensource.org/licenses/MIT>. // <https://opensource.org/licenses/MIT>.
use crate::{ use crate::{
frac::False,
helpers::IntHelper, helpers::IntHelper,
traits::Fixed, traits::Fixed,
types::{LeEqU128, LeEqU16, LeEqU32, LeEqU64, LeEqU8}, types::{LeEqU128, LeEqU16, LeEqU32, LeEqU64, LeEqU8},
@ -21,124 +22,182 @@ use crate::{
FixedU8, FixedU8,
}; };
use core::{ use core::{
cmp::Ordering, cmp::{self, Ordering},
fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, fmt::{
Alignment, Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult,
UpperHex,
},
mem, str, mem, str,
}; };
trait Radix2 { fn pad(
fn digit_bits(&self) -> u32; is_neg: bool,
fn prefix(&self) -> &'static str; maybe_prefix: &str,
fn encode_digits(&self, digits: &mut [u8]); 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 { #[derive(Clone, Copy)]
($Radix:ident($bits:expr, $prefix:expr), $($range:pat => $inc:expr),+) => { enum Radix2 {
#[derive(Clone, Copy)] Bin,
struct $Radix; Oct,
impl Radix2 for $Radix { LowHex,
#[inline] UpHex,
fn digit_bits(&self) -> u32 { }
$bits impl Radix2 {
} #[inline]
fn digit_bits(self) -> u32 {
#[inline] match self {
fn prefix(&self) -> &'static str { Radix2::Bin => 1,
$prefix Radix2::Oct => 3,
} Radix2::LowHex => 4,
Radix2::UpHex => 4,
#[inline] }
fn encode_digits(&self, digits: &mut [u8]) { }
for digit in digits.iter_mut() { #[inline]
*digit += match *digit { fn prefix(self) -> &'static str {
$($range => $inc,)+ match self {
_ => continue, 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' } fn fmt_radix2_helper<I>(
radix2! { Oct(3, "0o"), 0..=7 => b'0' } (is_neg, mut int, mut frac): (bool, I, I),
radix2! { LowHex(4, "0x"), 0..=9 => b'0', 10..=15 => b'a' - 10 } radix: Radix2,
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<F: FmtRadix2Helper>(
frac_bits: u32,
(is_neg, mut int, mut frac): (bool, F, F),
radix: &dyn Radix2,
fmt: &mut Formatter, fmt: &mut Formatter,
) -> FmtResult { ) -> FmtResult
let int_bits = F::NBITS - frac_bits; where
let digit_bits: u32 = radix.digit_bits(); I: IntHelper<IsSigned = False>,
// 128 binary digits, one radix point, one leading zero {
let mut buf: [u8; 130] = [0; 130]; let digit_bits = radix.digit_bits();
let max_int_digits = (int_bits + digit_bits - 1) / digit_bits; let compl_digit_bits = I::NBITS - digit_bits;
let frac_digits = (frac_bits + digit_bits - 1) / digit_bits; let int_digit_mask = !(!I::ZERO << digit_bits);
let (mut int_start, frac_start); // 128 binary digits, one radix point, one leading zero.
if max_int_digits == 0 { // The leading zero has two purposes:
buf[0] = b'0'; // 1. If there are no integer bits, we still want to print "0." instead of ".".
buf[1] = b'.'; // 2. If rounding causes a carry, we can carry into this extra zero.
int_start = 0; let mut buf = [0u8; 130];
frac_start = 2;
// 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 { } else {
int_start = max_int_digits; int_digits
for r in buf[0..max_int_digits as usize].iter_mut().rev() { };
*r = int.take_int_digit(digit_bits); let round_up = frac > I::MSB || (frac == I::MSB && buf[least_significant_index].is_odd());
int_start -= 1; if round_up {
if int == F::ZERO { for b in buf[0..int_digits + frac_digits + 2].iter_mut().rev() {
if *b < int_digit_mask.lower_byte() {
*b += 1;
break; 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; let end_zeros = fmt.precision().map(|x| x - frac_digits).unwrap_or(0);
if frac_digits == 0 { let begin = if buf[0] > 0 || buf[1] == b'.' { 0 } else { 1 };
end = frac_start - 1; let end = if frac_digits > 0 {
int_digits + frac_digits + 2
} else if end_zeros > 0 {
int_digits + 2
} else { } else {
end = frac_start + frac_digits; int_digits + 1
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[begin..end];
}
}
let used_buf = &mut buf[int_start as usize..end as usize];
radix.encode_digits(used_buf); radix.encode_digits(used_buf);
let str_buf = str::from_utf8(used_buf).unwrap(); 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] #[inline]
@ -164,12 +223,12 @@ where
} }
#[inline] #[inline]
fn fmt_radix2<F: Fixed>(num: F, radix: &dyn Radix2, fmt: &mut Formatter) -> FmtResult fn fmt_radix2<F: Fixed>(num: F, radix: Radix2, fmt: &mut Formatter) -> FmtResult
where where
F::Bits: IntHelper, F::Bits: IntHelper,
<F::Bits as IntHelper>::Unsigned: FmtRadix2Helper, <F::Bits as IntHelper>::Unsigned: IntHelper<IsSigned = False>,
{ {
fmt_radix2_helper(F::frac_nbits(), parts(num), radix, fmt) fmt_radix2_helper(parts(num), radix, fmt)
} }
macro_rules! impl_fmt { macro_rules! impl_fmt {
@ -188,25 +247,25 @@ macro_rules! impl_fmt {
impl<Frac: $LeEqU> Binary for $Fixed<Frac> { impl<Frac: $LeEqU> Binary for $Fixed<Frac> {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
fmt_radix2(*self, &Bin, f) fmt_radix2(*self, Radix2::Bin, f)
} }
} }
impl<Frac: $LeEqU> Octal for $Fixed<Frac> { impl<Frac: $LeEqU> Octal for $Fixed<Frac> {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
fmt_radix2(*self, &Oct, f) fmt_radix2(*self, Radix2::Oct, f)
} }
} }
impl<Frac: $LeEqU> LowerHex for $Fixed<Frac> { impl<Frac: $LeEqU> LowerHex for $Fixed<Frac> {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
fmt_radix2(*self, &LowHex, f) fmt_radix2(*self, Radix2::LowHex, f)
} }
} }
impl<Frac: $LeEqU> UpperHex for $Fixed<Frac> { impl<Frac: $LeEqU> UpperHex for $Fixed<Frac> {
fn fmt(&self, f: &mut Formatter) -> FmtResult { 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)] #[cfg(test)]
mod tests { mod tests {
use crate::{frac::Unsigned, *}; use crate::{frac::Unsigned, *};
use core::{ use std::{
fmt::{Debug, Error as FmtError, Formatter, Result as FmtResult, Write}, fmt::{Debug, Error as FmtError, Formatter, Result as FmtResult, Write},
mem, format, mem,
string::String,
}; };
struct Buf([u8; 256]); 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] #[test]
fn hex() { fn hex() {
use crate::frac::U7 as Frac; use crate::frac::U7 as Frac;
let frac = Frac::U32; let frac = Frac::U32;
for i in 0..(1 << frac) { for i in 0..(1u32 << frac) {
let p = 0x1234_5678_9abc_def0u64 ^ i as u64; let p = 0x1234_5678_9abc_def0u64 ^ u64::from(i);
let n = -0x1234_5678_9abc_def0i64 ^ i64::from(i); let n = -0x1234_5678_9abc_def0i64 ^ i64::from(i);
let f_p = FixedU64::<Frac>::from_bits(p); let f_p = FixedU64::<Frac>::from_bits(p);
let f_n = FixedI64::<Frac>::from_bits(n); let f_n = FixedI64::<Frac>::from_bits(n);
assert_eq_fmt!(("{:x}", f_p), ("{:x}.{:02x}", p >> frac, (p & 0x7f) << 1)); let mut check_p = format!("{:x}.{:02x}", p >> frac, (p & 0x7f) << 1);
assert_eq_fmt!( up_frac_digits(&mut check_p, 1000);
("{:x}", f_n), let trimmed_p = trim_frac_zeros(&check_p);
("-{:x}.{:02x}", n.abs() >> frac, (n.abs() & 0x7f) << 1) 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);
} }
} }

View File

@ -49,6 +49,8 @@ where
fn overflowing_add(self, val: Self) -> (Self, bool); fn overflowing_add(self, val: Self) -> (Self, bool);
fn overflowing_mul(self, val: Self) -> (Self, bool); fn overflowing_mul(self, val: Self) -> (Self, bool);
fn leading_zeros(self) -> u32; fn leading_zeros(self) -> u32;
fn trailing_zeros(self) -> u32;
fn lower_byte(self) -> u8;
fn to_fixed_helper( fn to_fixed_helper(
self, self,
@ -100,6 +102,16 @@ macro_rules! sealed_int {
self.leading_zeros() self.leading_zeros()
} }
#[inline]
fn trailing_zeros(self) -> u32 {
self.trailing_zeros()
}
#[inline]
fn lower_byte(self) -> u8 {
self as u8
}
#[inline] #[inline]
fn to_repr_fixed(self) -> Self::ReprFixed { fn to_repr_fixed(self) -> Self::ReprFixed {
Self::ReprFixed::from_bits(self.int_repr()) Self::ReprFixed::from_bits(self.int_repr())