diff --git a/src/display.rs b/src/display.rs index 6a8d3a1..136c035 100644 --- a/src/display.rs +++ b/src/display.rs @@ -2,14 +2,13 @@ use std::cmp::Ordering; use std::fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}; use std::mem; use std::str; +use FixedNum; use { FixedI128, FixedI16, FixedI32, FixedI64, FixedI8, FixedU128, FixedU16, FixedU32, FixedU64, FixedU8, }; -const F: u32 = 7; - trait Radix2 { const BITS: u8; fn radix() -> u8; @@ -46,123 +45,6 @@ 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 FixedNum: Copy { - type Part; - fn parts(self) -> (bool, Self::Part, Self::Part); - #[inline(always)] - fn int_bits() -> u32 { - mem::size_of::() as u32 * 8 - F - } - #[inline(always)] - fn frac_bits() -> u32 { - F - } - fn take_int_digit(int_part: &mut Self::Part, digit_bits: u32) -> u8; - fn take_frac_digit(frac_part: &mut Self::Part, digit_bits: u32) -> u8; - fn take_int_dec_digit(int_part: &mut Self::Part) -> u8; - fn take_frac_dec_digit(int_part: &mut Self::Part) -> u8; - fn part_is_zero(part: &Self::Part) -> bool; - fn part_cmp_half(part: &Self::Part) -> Ordering; -} - -macro_rules! fixed_num_common { - ($Fixed:ident($Part:ty); $($rem:tt)+) => { - impl FixedNum for $Fixed { - type Part = $Part; - - $($rem)+ - - #[inline] - fn take_int_digit(int_part: &mut $Part, digit_bits: u32) -> u8 { - let mask = (1 << digit_bits) - 1; - let ret = (*int_part & mask) as u8; - *int_part >>= digit_bits; - ret - } - - #[inline] - fn take_frac_digit(frac_part: &mut $Part, digit_bits: u32) -> u8 { - let rem_bits = mem::size_of::<$Part>() as u32 * 8 - digit_bits; - let mask = !0 << rem_bits; - let ret = ((*frac_part & mask) >> rem_bits) as u8; - *frac_part <<= digit_bits; - ret - } - - #[inline] - fn take_int_dec_digit(int_part: &mut $Part) -> u8 { - println!("int part {}", int_part); - let ret = (*int_part % 10) as u8; - *int_part /= 10; - ret - } - - #[inline] - fn take_frac_dec_digit(frac_part: &mut $Part) -> u8 { - let next = frac_part.wrapping_mul(10); - let ret = ((*frac_part - next / 10) / (!0 / 10)) as u8; - *frac_part = next; - ret - } - - #[inline] - fn part_is_zero(part: &$Part) -> bool { - *part == 0 - } - - #[inline] - fn part_cmp_half(part: &$Part) -> Ordering { - part.cmp(&!(!0 >> 1)) - } - } - }; -} - -macro_rules! fixed_num_unsigned { - ($Fixed:ident($Part:ty)) => { - fixed_num_common! { - $Fixed($Part); - #[inline] - fn parts(self) -> (bool, $Part, $Part) { - let bits = self.to_bits(); - let int_bits = <$Fixed as FixedNum>::int_bits(); - let frac_bits = <$Fixed as FixedNum>::frac_bits(); - let int_part = if int_bits == 0 { 0 } else { bits >> frac_bits }; - let frac_part = if frac_bits == 0 { 0 } else { bits << int_bits }; - (false, int_part, frac_part) - } - } - }; -} - -macro_rules! fixed_num_signed { - ($Fixed:ident($Part:ty)) => { - fixed_num_common! { - $Fixed($Part); - #[inline] - fn parts(self) -> (bool, $Part, $Part) { - let bits = self.to_bits().wrapping_abs() as $Part; - let int_bits = <$Fixed as FixedNum>::int_bits(); - let frac_bits = <$Fixed as FixedNum>::frac_bits(); - let int_part = if int_bits == 0 { 0 } else { bits >> frac_bits }; - let frac_part = if frac_bits == 0 { 0 } else { bits << int_bits }; - (self.0 < 0, int_part,frac_part) - } - } - }; -} - -fixed_num_unsigned! { FixedU8(u8) } -fixed_num_unsigned! { FixedU16(u16) } -fixed_num_unsigned! { FixedU32(u32) } -fixed_num_unsigned! { FixedU64(u64) } -fixed_num_unsigned! { FixedU128(u128) } -fixed_num_signed! { FixedI8(u8) } -fixed_num_signed! { FixedI16(u16) } -fixed_num_signed! { FixedI32(u32) } -fixed_num_signed! { FixedI64(u64) } -fixed_num_signed! { FixedI128(u128) } - fn fmt_radix2(num: F, _radix: R, fmt: &mut Formatter) -> FmtResult { let digit_bits: u32 = R::BITS.into(); let (int_bits, frac_bits) = (F::int_bits(), F::frac_bits()); diff --git a/src/lib.rs b/src/lib.rs index 1b2ec37..40ff04c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,17 @@ Coming soon (waiting on [const generics]). #![doc(html_root_url = "https://docs.rs/rug/0.0.0")] #![doc(test(attr(deny(warnings))))] +mod display; +mod traits; + +use std::f32; +use std::f64; use std::mem; use std::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Sub, SubAssign, }; - -mod display; +use traits::FixedNum; const F: u32 = 7; @@ -112,6 +116,79 @@ macro_rules! doc_comment { }; } +macro_rules! to_f { + ($method:ident -> $f:ident($u:ident), $exp_bits:expr, $prec:expr) => { + doc_comment! { + concat!( + "Converts the fixed-point number to `", + stringify!($f), + "`." + ), + pub fn $method(self) -> $f { + // exponent is IEEE754 style (1 <= significand < 2) + let exp_max = (1 << ($exp_bits - 1)) - 1; + let exp_min = 1 - exp_max; + let (int_bits, frac_bits) = (Self::int_bits(), Self::frac_bits()); + + let (neg, int, frac) = self.parts(); + let int_frac = (int << frac_bits) | (frac >> int_bits); + let leading_zeros = int_frac.leading_zeros(); + let signif_bits = int_bits + frac_bits - leading_zeros; + if signif_bits == 0 { + debug_assert!(!neg); + return 0.0; + } + // remove leading zeros and implicit one + let mut mantissa = int_frac << leading_zeros << 1; + let exponent = int_bits as i32 - 1 - leading_zeros as i32; + let biased_exponent = if exponent > exp_max { + return if neg { $f::NEG_INFINITY } else { $f::INFINITY }; + } else if exponent < exp_min { + let lost_prec = exp_min - exponent; + if lost_prec as u32 >= (int_bits + frac_bits) { + mantissa = 0; + } else { + // reinsert implicit one + mantissa = (mantissa >> 1) | !(!0 >> 1); + mantissa >>= lost_prec - 1; + } + 0 + } else { + (exponent + exp_max) as $u + }; + // check for rounding + let round_up = (int_bits + frac_bits >= $prec) && { + let shift = $prec - 1; + let mid_bit = !(!0 >> 1) >> shift; + let lower_bits = mid_bit - 1; + if mantissa & mid_bit == 0 { + false + } else if mantissa & lower_bits != 0 { + true + } else { + // round to even + mantissa & (mid_bit << 1) != 0 + } + }; + let bits_sign = if neg { !(!0 >> 1) } else { 0 }; + let bits_exp = biased_exponent << ($prec - 1); + let bits_mantissa = (if int_bits + frac_bits >= $prec - 1 { + (mantissa >> (int_bits + frac_bits - $prec + 1)) as $u + } else { + (mantissa as $u) << ($prec - 1 - int_bits - frac_bits) + }) & !(!0 << ($prec - 1)); + let mut bits_exp_mantissa = bits_exp | bits_mantissa; + if round_up { + // cannot be infinite already + debug_assert!(bits_exp_mantissa != !0 >> 1); + bits_exp_mantissa += 1; + } + $f::from_bits(bits_sign | bits_exp_mantissa) + } + } + }; +} + macro_rules! fixed_unsigned { ($(#[$attr:meta])* $Fixed:ident($Inner:ty)) => { #[derive(Clone, Copy)] @@ -148,6 +225,8 @@ macro_rules! fixed_unsigned { } } + to_f! { to_f32 -> f32(u32), 8, 24 } + to_f! { to_f64 -> f64(u64), 11, 53 } } pass! { impl Add for $Fixed($Inner) { add } } @@ -354,4 +433,94 @@ mod tests { assert_eq!((-af).to_bits(), -(a << F)); assert_eq!((!af).to_bits(), !(a << F)); } + + #[test] + fn to_f32() { + for u in 0x00..=0xff { + let fu = FixedU8::from_bits(u); + assert_eq!(fu.to_f32(), u as f32 / 128.0); + let i = u as i8; + let fi = FixedI8::from_bits(i); + assert_eq!(fi.to_f32(), i as f32 / 128.0); + + for hi in &[ + 0u32, + 0x0000_0100, + 0x7fff_ff00, + 0x8000_0000, + 0x8100_0000, + 0xffff_fe00, + 0xffff_ff00, + ] { + let uu = *hi | u as u32; + let fuu = FixedU32::from_bits(uu); + assert_eq!(fuu.to_f32(), uu as f32 / 128.0); + let ii = uu as i32; + let fii = FixedI32::from_bits(ii); + assert_eq!(fii.to_f32(), ii as f32 / 128.0); + } + + for hi in &[ + 0u128, + 0x0000_0000_0000_0000_0000_0000_0000_0100, + 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ff00, + 0x8000_0000_0000_0000_0000_0000_0000_0000, + 0x8100_0000_0000_0000_0000_0000_0000_0000, + 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_fe00, + 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ff00, + ] { + let uu = *hi | u as u128; + let fuu = FixedU128::from_bits(uu); + assert_eq!(fuu.to_f32(), (uu as f64 / 128.0) as f32); + let ii = uu as i128; + let fii = FixedI128::from_bits(ii); + assert_eq!(fii.to_f32(), (ii as f64 / 128.0) as f32); + } + } + } + + #[test] + fn to_f64() { + for u in 0x00..=0xff { + let fu = FixedU8::from_bits(u); + assert_eq!(fu.to_f32(), u as f32 / 128.0); + let i = u as i8; + let fi = FixedI8::from_bits(i); + assert_eq!(fi.to_f32(), i as f32 / 128.0); + + for hi in &[ + 0u64, + 0x0000_0000_0000_0100, + 0x7fff_ffff_ffff_ff00, + 0x8000_0000_0000_0000, + 0x8100_0000_0000_0000, + 0xffff_ffff_ffff_fe00, + 0xffff_ffff_ffff_ff00, + ] { + let uu = *hi | u as u64; + let fuu = FixedU64::from_bits(uu); + assert_eq!(fuu.to_f64(), uu as f64 / 128.0); + let ii = uu as i64; + let fii = FixedI64::from_bits(ii); + assert_eq!(fii.to_f64(), ii as f64 / 128.0); + } + + for hi in &[ + 0u128, + 0x0000_0000_0000_0000_0000_0000_0000_0100, + 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ff00, + 0x8000_0000_0000_0000_0000_0000_0000_0000, + 0x8100_0000_0000_0000_0000_0000_0000_0000, + 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_fe00, + 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ff00, + ] { + let uu = *hi | u as u128; + let fuu = FixedU128::from_bits(uu); + assert_eq!(fuu.to_f64(), uu as f64 / 128.0); + let ii = uu as i128; + let fii = FixedI128::from_bits(ii); + assert_eq!(fii.to_f64(), ii as f64 / 128.0); + } + } + } } diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..7f4365f --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,126 @@ +use std::cmp::Ordering; +use std::mem; + +use { + FixedI128, FixedI16, FixedI32, FixedI64, FixedI8, FixedU128, FixedU16, FixedU32, FixedU64, + FixedU8, +}; + +const F: u32 = 7; + +pub(crate) trait FixedNum: Copy { + type Part; + fn parts(self) -> (bool, Self::Part, Self::Part); + #[inline(always)] + fn int_bits() -> u32 { + mem::size_of::() as u32 * 8 - F + } + #[inline(always)] + fn frac_bits() -> u32 { + F + } + fn take_int_digit(int_part: &mut Self::Part, digit_bits: u32) -> u8; + fn take_frac_digit(frac_part: &mut Self::Part, digit_bits: u32) -> u8; + fn take_int_dec_digit(int_part: &mut Self::Part) -> u8; + fn take_frac_dec_digit(int_part: &mut Self::Part) -> u8; + fn part_is_zero(part: &Self::Part) -> bool; + fn part_cmp_half(part: &Self::Part) -> Ordering; +} + +macro_rules! fixed_num_common { + ($Fixed:ident($Part:ty); $($rem:tt)+) => { + impl FixedNum for $Fixed { + type Part = $Part; + + $($rem)+ + + #[inline] + fn take_int_digit(int_part: &mut $Part, digit_bits: u32) -> u8 { + let mask = (1 << digit_bits) - 1; + let ret = (*int_part & mask) as u8; + *int_part >>= digit_bits; + ret + } + + #[inline] + fn take_frac_digit(frac_part: &mut $Part, digit_bits: u32) -> u8 { + let rem_bits = mem::size_of::<$Part>() as u32 * 8 - digit_bits; + let mask = !0 << rem_bits; + let ret = ((*frac_part & mask) >> rem_bits) as u8; + *frac_part <<= digit_bits; + ret + } + + #[inline] + fn take_int_dec_digit(int_part: &mut $Part) -> u8 { + println!("int part {}", int_part); + let ret = (*int_part % 10) as u8; + *int_part /= 10; + ret + } + + #[inline] + fn take_frac_dec_digit(frac_part: &mut $Part) -> u8 { + let next = frac_part.wrapping_mul(10); + let ret = ((*frac_part - next / 10) / (!0 / 10)) as u8; + *frac_part = next; + ret + } + + #[inline] + fn part_is_zero(part: &$Part) -> bool { + *part == 0 + } + + #[inline] + fn part_cmp_half(part: &$Part) -> Ordering { + part.cmp(&!(!0 >> 1)) + } + } + }; +} + +macro_rules! fixed_num_unsigned { + ($Fixed:ident($Part:ty)) => { + fixed_num_common! { + $Fixed($Part); + #[inline] + fn parts(self) -> (bool, $Part, $Part) { + let bits = self.to_bits(); + let int_bits = <$Fixed as FixedNum>::int_bits(); + let frac_bits = <$Fixed as FixedNum>::frac_bits(); + let int_part = if int_bits == 0 { 0 } else { bits >> frac_bits }; + let frac_part = if frac_bits == 0 { 0 } else { bits << int_bits }; + (false, int_part, frac_part) + } + } + }; +} + +macro_rules! fixed_num_signed { + ($Fixed:ident($Part:ty)) => { + fixed_num_common! { + $Fixed($Part); + #[inline] + fn parts(self) -> (bool, $Part, $Part) { + let bits = self.to_bits().wrapping_abs() as $Part; + let int_bits = <$Fixed as FixedNum>::int_bits(); + let frac_bits = <$Fixed as FixedNum>::frac_bits(); + let int_part = if int_bits == 0 { 0 } else { bits >> frac_bits }; + let frac_part = if frac_bits == 0 { 0 } else { bits << int_bits }; + (self.0 < 0, int_part,frac_part) + } + } + }; +} + +fixed_num_unsigned! { FixedU8(u8) } +fixed_num_unsigned! { FixedU16(u16) } +fixed_num_unsigned! { FixedU32(u32) } +fixed_num_unsigned! { FixedU64(u64) } +fixed_num_unsigned! { FixedU128(u128) } +fixed_num_signed! { FixedI8(u8) } +fixed_num_signed! { FixedI16(u16) } +fixed_num_signed! { FixedI32(u32) } +fixed_num_signed! { FixedI64(u64) } +fixed_num_signed! { FixedI128(u128) }