// Copyright © 2018–2019 Trevor Spiteri // This library is free software: you can redistribute it and/or // modify it under the terms of either // // * the Apache License, Version 2.0 or // * the MIT License // // at your option. // // You should have recieved copies of the Apache License and the MIT // License along with the library. If not, see // and // . use { crate::{ frac::{IsLessOrEqual, True, Unsigned, U128, U16, U32, U64, U8}, sealed::{SealedFixed, SealedInt}, FixedI128, FixedI16, FixedI32, FixedI64, FixedI8, FixedU128, FixedU16, FixedU32, FixedU64, FixedU8, }, core::{ cmp::Ordering, fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, str, }, }; trait Radix2 { fn digit_bits(&self) -> u32; fn prefix(&self) -> &'static str; fn encode_digits(&self, digits: &mut [u8]); } 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, }; } } } }; } 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: SealedInt { 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, fmt: &mut Formatter, ) -> FmtResult where F: FmtRadix2Helper, { 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; } 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.is_zero() { break; } } buf[max_int_digits as usize] = b'.'; frac_start = max_int_digits + 1; } let end; if frac_digits == 0 { end = frac_start - 1; } 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]; radix.encode_digits(used_buf); let str_buf = str::from_utf8(used_buf).unwrap(); fmt.pad_integral(!is_neg, radix.prefix(), str_buf) } #[inline] fn fmt_radix2(num: F, radix: &dyn Radix2, fmt: &mut Formatter) -> FmtResult where F: SealedFixed, Bits: SealedInt, Bits::Unsigned: FmtRadix2Helper, { fmt_radix2_helper(F::FRAC_NBITS, num.parts(), radix, fmt) } macro_rules! impl_fmt { ($($Fixed:ident($Len:ty))*) => { $( impl Display for $Fixed where Frac: Unsigned + IsLessOrEqual<$Len, Output = True>, { fn fmt(&self, f: &mut Formatter) -> FmtResult { fmt_dec(*self, f) } } impl Debug for $Fixed where Frac: Unsigned + IsLessOrEqual<$Len, Output = True>, { fn fmt(&self, f: &mut Formatter) -> FmtResult { fmt_dec(*self, f) } } impl Binary for $Fixed where Frac: Unsigned + IsLessOrEqual<$Len, Output = True>, { fn fmt(&self, f: &mut Formatter) -> FmtResult { fmt_radix2(*self, &Bin, f) } } impl Octal for $Fixed where Frac: Unsigned + IsLessOrEqual<$Len, Output = True>, { fn fmt(&self, f: &mut Formatter) -> FmtResult { fmt_radix2(*self, &Oct, f) } } impl LowerHex for $Fixed where Frac: Unsigned + IsLessOrEqual<$Len, Output = True>, { fn fmt(&self, f: &mut Formatter) -> FmtResult { fmt_radix2(*self, &LowHex, f) } } impl UpperHex for $Fixed where Frac: Unsigned + IsLessOrEqual<$Len, Output = True>, { fn fmt(&self, f: &mut Formatter) -> FmtResult { fmt_radix2(*self, &UpHex, f) } } )* }; } impl_fmt! { FixedU8(U8) FixedU16(U16) FixedU32(U32) FixedU64(U64) FixedU128(U128) } impl_fmt! { FixedI8(U8) FixedI16(U16) FixedI32(U32) FixedI64(U64) FixedI128(U128) } fn dec_int_digits(int_bits: u32) -> u32 { assert!(int_bits < 299); if int_bits == 0 { return 0; } let i = if int_bits >= 196 { 12 } else if int_bits >= 103 { 11 } else { 10 }; (int_bits * 3 + i) / 10 } fn dec_frac_digits(frac_bits: u32) -> u32 { assert!(frac_bits < 299); let i = if frac_bits >= 196 { 12 } else if frac_bits >= 103 { 11 } else { 10 }; (frac_bits * 3 + i) / 10 } trait FmtDecHelper: SealedInt { fn cmp_half(&self) -> Ordering; fn take_int_digit(&mut self) -> u8; fn take_frac_digit(&mut self) -> u8; } macro_rules! fmt_dec_helper { ($($UInner:ty)*) => { $( impl FmtDecHelper for $UInner { #[inline] fn cmp_half(&self) -> Ordering { self.cmp(&<$UInner>::MSB) } #[inline] fn take_int_digit(&mut self) -> u8 { let ret = (*self % 10) as u8; *self /= 10; ret } #[inline] fn take_frac_digit(&mut self) -> u8 { let next = self.wrapping_mul(10); let ret = ((*self - next / 10) / (!0 / 10)) as u8; *self = next; ret } } )* }; } fmt_dec_helper! { u8 u16 u32 u64 u128 } fn fmt_dec_helper( frac_bits: u32, (is_neg, mut int, mut frac): (bool, F, F), fmt: &mut Formatter, ) -> FmtResult where F: FmtDecHelper, { let int_bits = F::NBITS - frac_bits; // 40 int digits // + 128 frac digits // + 1 dec point, // + 1 leading zero or padding for carry due to rounding up, // = 170 let mut buf: [u8; 170] = [0; 170]; let max_int_digits = dec_int_digits(int_bits); let req_frac_digits = dec_frac_digits(frac_bits); // precision is limited to frac bits, which would always print // exact non-rounded number anyway let frac_digits = if let Some(prec) = fmt.precision().map(|x| x as u32) { if prec > frac_bits { frac_bits } else { prec } } else { req_frac_digits }; let mut int_start; let frac_start; if max_int_digits == 0 { buf[0] = b'0'; buf[1] = b'.'; int_start = 0; frac_start = 2; } else { // pad by one in case rounding results in another digit int_start = max_int_digits + 1; buf[int_start as usize] = b'.'; frac_start = int_start + 1; for r in buf[1..int_start as usize].iter_mut().rev() { *r = b'0' + int.take_int_digit(); int_start -= 1; if int.is_zero() { break; } } } let end; if frac_digits == 0 { end = frac_start - 1; } else { end = frac_start + frac_digits; for r in buf[frac_start as usize..end as usize].iter_mut() { *r = b'0' + frac.take_frac_digit(); } // check for rounding up let round_up = match frac.cmp(&F::MSB) { Ordering::Less => false, Ordering::Greater => true, Ordering::Equal => { let last_digit = buf[(end - 1) as usize]; debug_assert!(b'0' <= last_digit && last_digit <= b'9'); // round up only if odd, so that we round to even (last_digit & 1) != 0 } }; if round_up { let mut done = false; for r in buf[int_start as usize..end as usize].iter_mut().rev() { if *r == b'9' { *r = b'0'; } else if *r != b'.' { *r += 1; done = true; break; } } if !done { int_start -= 1; buf[int_start as usize] = b'1'; } } } let buf = str::from_utf8(&buf[int_start as usize..end as usize]).unwrap(); fmt.pad_integral(!is_neg, "", buf) } #[inline] fn fmt_dec(num: F, fmt: &mut Formatter) -> FmtResult where F: SealedFixed, Bits: SealedInt, Bits::Unsigned: FmtDecHelper, { fmt_dec_helper(F::FRAC_NBITS, num.parts(), fmt) } #[cfg(test)] mod tests { use crate::*; use core::fmt::{Debug, Error as FmtError, Formatter, Result as FmtResult, Write}; use core::mem; struct Buf([u8; 256]); impl Buf { fn new() -> Buf { Buf([0u8; 256]) } fn target(&mut self) -> BufSlice { BufSlice(&mut self.0) } } struct BufSlice<'a>(&'a mut [u8]); impl<'a> Write for BufSlice<'a> { fn write_str(&mut self, s: &str) -> FmtResult { let s_len = s.len(); if s_len > self.0.len() { Err(FmtError) } else { self.0[..s_len].copy_from_slice(s.as_bytes()); let rem = mem::replace(&mut self.0, &mut []); self.0 = &mut rem[s_len..]; Ok(()) } } } impl Eq for Buf {} impl PartialEq for Buf { fn eq(&self, rhs: &Buf) -> bool { self.0.iter().zip(rhs.0.iter()).all(|(a, b)| a == b) } } impl Debug for Buf { fn fmt(&self, f: &mut Formatter) -> FmtResult { f.write_str("\"")?; for &i in &self.0[..] { if i == 0 { break; } else if i < 0x20 || i > 0x7f { write!(f, "\\x{:02x}", i)?; } else { f.write_char(i as char)?; } } f.write_str("\"") } } macro_rules! assert_eq_fmt { (($f1:expr, $($arg1:tt)*), ($f2:expr, $($arg2:tt)*)) => {{ let mut buf1 = Buf::new(); write!(buf1.target(), $f1, $($arg1)*).unwrap(); let mut buf2 = Buf::new(); write!(buf2.target(), $f2, $($arg2)*).unwrap(); assert_eq!(buf1, buf2); }}; } #[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; 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) ); } } #[test] fn dec() { use crate::frac::U7 as Frac; let frac = Frac::U32; for i in 0..(1 << frac) { let bits = !0u32 ^ i; let flt = f64::from(bits) / f64::from(frac).exp2(); let fix = FixedU32::::from_bits(bits); assert_eq_fmt!(("{}", fix), ("{:.3}", flt)); } } fn pow(base: u32, mut exp: u32) -> f64 { let mut mult = f64::from(base); let mut result = 1.0; loop { if exp % 2 != 0 { result *= mult; } exp /= 2; if exp == 0 { break; } mult *= mult; } result } #[test] fn dec_int_digits() { use super::dec_int_digits; assert_eq!(dec_int_digits(0), 0); assert_eq!(dec_int_digits(1), 1); for int_bits in 2..299 { let check = (pow(2, int_bits) - 1.0).log10().ceil() as u32; assert_eq!(dec_int_digits(int_bits), check, "int_bits {}", int_bits); } } #[test] fn dec_frac_digits() { use super::dec_frac_digits; for frac_bits in 0..299 { let error = 1.0 / pow(10, dec_frac_digits(frac_bits)); let error_with_one_less_dec_digit = error * 10.0; let delta = 1.0 / pow(2, frac_bits); assert!( error < delta, "frac_bits {}, error {:e}, delta {:e}", frac_bits, error, delta ); assert!( error_with_one_less_dec_digit >= delta, "frac_bits {}, error with one less digit {:e}, delta {:e}", frac_bits, error_with_one_less_dec_digit, delta ); } } }