From f4967afa05c7e6ba53c016be554a207108d387ed Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Wed, 26 Jun 2024 20:17:05 -0400 Subject: [PATCH 01/11] Add unit tests for trait method `float::Float::integer_decode` --- src/float.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/float.rs b/src/float.rs index d0d21a50..bf4c05ed 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2397,6 +2397,99 @@ mod tests { check::(1e-12); } + /// Test the behavior of `Float::integer_decode` with the `given` input and `expected` output values. + #[cfg(any(feature = "std", feature = "libm"))] + fn test_integer_decode(given: T, expected: (u64, i16, i8)) + where + T: crate::float::Float + core::fmt::LowerExp + core::fmt::Debug, + { + use crate::float::Float; + + let found = Float::integer_decode(given); + + assert!( + expected == found, + "unexpected output of `Float::integer_decode({0:e})` +\texpected: ({1:#x} {2} {3}) +\t found: {4:#x} {5} {6}", + given, + expected.0, + expected.1, + expected.2, + found.0, + found.1, + found.2 + ); + + // Destructure the `found` output and cast values as float. + let mantissa_f = T::from(found.0).unwrap(); + let exponent_f = T::from(found.1).unwrap(); + let sign_f = T::from(found.2).unwrap(); + + // Recover the `given` input using equation: sign * mantissa * 2^exponent. + let recovered = sign_f * mantissa_f * exponent_f.exp2(); + let deviation = recovered - given; + let tolerance = T::from(1e-6).unwrap(); + + assert_eq!(T::one(), tolerance.signum(), "tolerance must be positive"); + assert!( + recovered == given || deviation.abs() < tolerance, + "absolute deviation must not exceed tolerance` +\t given: {:+e} +\trecovered: {:+e} +\tdeviation: {:+e} +\ttolerance: <{:e}", + given, + recovered, + deviation, + tolerance + ); + } + + #[test] + #[cfg(any(feature = "std", feature = "libm"))] + fn integer_decode_f32() { + for sign in [1, -1] { + let sign_f = sign as f32; + test_integer_decode(sign_f * 0.0__f32, (0x000000, -150, sign)); + test_integer_decode(sign_f * f32::MIN_POSITIVE, (0x800000, -149, sign)); + test_integer_decode(sign_f * 0.25_f32, (0x800000, -25, sign)); + test_integer_decode(sign_f * 0.5__f32, (0x800000, -24, sign)); + test_integer_decode(sign_f * 1____f32, (0x800000, -23, sign)); + test_integer_decode(sign_f * 1.5__f32, (0xc00000, -23, sign)); + test_integer_decode(sign_f * 2____f32, (0x800000, -22, sign)); + test_integer_decode(sign_f * 2.5__f32, (0xa00000, -22, sign)); + test_integer_decode(sign_f * 3____f32, (0xc00000, -22, sign)); + test_integer_decode(sign_f * 4____f32, (0x800000, -21, sign)); + test_integer_decode(sign_f * 5____f32, (0xa00000, -21, sign)); + test_integer_decode(sign_f * 42___f32, (0xa80000, -18, sign)); + test_integer_decode(sign_f * f32::MAX, (0xffffff, 104, sign)); + test_integer_decode(sign_f * f32::INFINITY, (0x800000, 105, sign)); + } + } + + #[test] + #[cfg(any(feature = "std", feature = "libm"))] + fn integer_decode_f64() { + for sign in [1, -1] { + let sign_f = sign as f64; + test_integer_decode(sign_f * 0.0__f64, (0x00000000000000, -1075, sign)); + test_integer_decode(sign_f * f64::MIN_POSITIVE, (0x10000000000000, -1074, sign)); + test_integer_decode(sign_f * 0.25_f64, (0x10000000000000, -54, sign)); + test_integer_decode(sign_f * 0.5__f64, (0x10000000000000, -53, sign)); + test_integer_decode(sign_f * 1____f64, (0x10000000000000, -52, sign)); + test_integer_decode(sign_f * 1.5__f64, (0x18000000000000, -52, sign)); + test_integer_decode(sign_f * 2____f64, (0x10000000000000, -51, sign)); + test_integer_decode(sign_f * 2.5__f64, (0x14000000000000, -51, sign)); + test_integer_decode(sign_f * 3____f64, (0x18000000000000, -51, sign)); + test_integer_decode(sign_f * 4____f64, (0x10000000000000, -50, sign)); + test_integer_decode(sign_f * 5____f64, (0x14000000000000, -50, sign)); + test_integer_decode(sign_f * 42___f64, (0x15000000000000, -47, sign)); + test_integer_decode(sign_f * f64::MAX, (0x1fffffffffffff, 971, sign)); + test_integer_decode(sign_f * f64::INFINITY, (0x10000000000000, 972, sign)); + } + } + #[test] #[cfg(any(feature = "std", feature = "libm"))] fn copysign() { From 25eb45fa0c028ee7a42a39540acdcf74c267520f Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Thu, 27 Jun 2024 15:18:31 -0400 Subject: [PATCH 02/11] Add FIXME related to NaN inputs to `float::Float::integer_decode` --- src/float.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/float.rs b/src/float.rs index bf4c05ed..7c35cbfc 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2466,6 +2466,8 @@ mod tests { test_integer_decode(sign_f * f32::MAX, (0xffffff, 104, sign)); test_integer_decode(sign_f * f32::INFINITY, (0x800000, 105, sign)); } + // FIXME: Unclear if we should be able to recover NaN inputs + // test_integer_decode(f32::NAN, (0xc00000, 105, 1)); } #[test] @@ -2488,6 +2490,8 @@ mod tests { test_integer_decode(sign_f * f64::MAX, (0x1fffffffffffff, 971, sign)); test_integer_decode(sign_f * f64::INFINITY, (0x10000000000000, 972, sign)); } + // FIXME: Unclear if we should be able to recover NaN inputs + // test_integer_decode(f64::NAN, (0x18000000000000, 972, 1)); } #[test] From 9232b284677f813a316416d10fe38a487fe0c9ce Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Thu, 27 Jun 2024 20:34:30 -0400 Subject: [PATCH 03/11] Fix assertion message in `test_integer_decode` --- src/float.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/float.rs b/src/float.rs index 7c35cbfc..5ff1e4ae 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2411,7 +2411,7 @@ mod tests { expected == found, "unexpected output of `Float::integer_decode({0:e})` \texpected: ({1:#x} {2} {3}) -\t found: {4:#x} {5} {6}", +\t found: ({4:#x} {5} {6})", given, expected.0, expected.1, From a6e2912fa817eb4f532fb21d881ab876ce22e4d2 Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Fri, 28 Jun 2024 13:55:52 -0400 Subject: [PATCH 04/11] Update doc strings to clarify which numbers are recoverable --- src/float.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/float.rs b/src/float.rs index a1e424f5..825cf1c8 100644 --- a/src/float.rs +++ b/src/float.rs @@ -775,7 +775,9 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { fn to_radians(self) -> Self; /// Returns the mantissa, base 2 exponent, and sign as integers, respectively. + /// /// The original number can be recovered by `sign * mantissa * 2 ^ exponent`. + /// This formula only works for zero, normal, and infinite numbers (per `classify()`) /// /// # Examples /// @@ -1861,7 +1863,9 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg { fn atanh(self) -> Self; /// Returns the mantissa, base 2 exponent, and sign as integers, respectively. + /// /// The original number can be recovered by `sign * mantissa * 2 ^ exponent`. + /// This formula only works for zero, normal, and infinite numbers (per `classify()`) /// /// ``` /// use num_traits::Float; From 300a7f91c78529e2423195f32b373209edd4256d Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Fri, 28 Jun 2024 13:56:23 -0400 Subject: [PATCH 05/11] Remove FIXME related to NaN inputs --- src/float.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/float.rs b/src/float.rs index 825cf1c8..9527e4ae 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2470,8 +2470,6 @@ mod tests { test_integer_decode(sign_f * f32::MAX, (0xffffff, 104, sign)); test_integer_decode(sign_f * f32::INFINITY, (0x800000, 105, sign)); } - // FIXME: Unclear if we should be able to recover NaN inputs - // test_integer_decode(f32::NAN, (0xc00000, 105, 1)); } #[test] @@ -2494,8 +2492,6 @@ mod tests { test_integer_decode(sign_f * f64::MAX, (0x1fffffffffffff, 971, sign)); test_integer_decode(sign_f * f64::INFINITY, (0x10000000000000, 972, sign)); } - // FIXME: Unclear if we should be able to recover NaN inputs - // test_integer_decode(f64::NAN, (0x18000000000000, 972, 1)); } #[test] From 6309a5399396db5db7f27486e223fc0aac7f42ee Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Fri, 28 Jun 2024 15:20:17 -0400 Subject: [PATCH 06/11] Add test cases for subnormal inputs to `Float::integer_decode` --- src/float.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/float.rs b/src/float.rs index 9527e4ae..a9be2527 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2456,6 +2456,7 @@ mod tests { for sign in [1, -1] { let sign_f = sign as f32; test_integer_decode(sign_f * 0.0__f32, (0x000000, -150, sign)); + test_integer_decode(sign_f * 1.0e-40_f32, (0x022d84, -150, sign)); // subnormal (between 0 and MIN_POSITIVE) test_integer_decode(sign_f * f32::MIN_POSITIVE, (0x800000, -149, sign)); test_integer_decode(sign_f * 0.25_f32, (0x800000, -25, sign)); test_integer_decode(sign_f * 0.5__f32, (0x800000, -24, sign)); @@ -2478,6 +2479,7 @@ mod tests { for sign in [1, -1] { let sign_f = sign as f64; test_integer_decode(sign_f * 0.0__f64, (0x00000000000000, -1075, sign)); + test_integer_decode(sign_f * 1.0e-308_f64, (0x0e61acf033d1a4, -1075, sign)); // subnormal (between 0 and MIN_POSITIVE) test_integer_decode(sign_f * f64::MIN_POSITIVE, (0x10000000000000, -1074, sign)); test_integer_decode(sign_f * 0.25_f64, (0x10000000000000, -54, sign)); test_integer_decode(sign_f * 0.5__f64, (0x10000000000000, -53, sign)); From c3b277a6261ce068f819e28a8a2c59000d4724d3 Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Fri, 28 Jun 2024 16:16:39 -0400 Subject: [PATCH 07/11] Add comments to mantissa calculation --- src/float.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/float.rs b/src/float.rs index a9be2527..cd950137 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2055,8 +2055,10 @@ fn integer_decode_f32(f: f32) -> (u64, i16, i8) { let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; let mantissa = if exponent == 0 { + // Zeros and subnormals (bits & 0x7fffff) << 1 } else { + // Normals, infinities, and NaN (bits & 0x7fffff) | 0x800000 }; // Exponent bias + mantissa shift @@ -2069,8 +2071,10 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) { let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; let mantissa = if exponent == 0 { + // Zeros and subnormals (bits & 0xfffffffffffff) << 1 } else { + // Normals, infinities, and NaN (bits & 0xfffffffffffff) | 0x10000000000000 }; // Exponent bias + mantissa shift From 3c09565ba25c81cf5c03bdf499a98cc8962bc4ed Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Wed, 26 Jun 2024 20:31:12 -0400 Subject: [PATCH 08/11] Refactor functions `integer_decode_f32` and `integer_decode_f64` with new macro `integer_decode\!` --- src/float.rs | 86 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/src/float.rs b/src/float.rs index cd950137..f730784a 100644 --- a/src/float.rs +++ b/src/float.rs @@ -4,6 +4,7 @@ use core::ops::{Add, Div, Neg}; use core::f32; use core::f64; +use std::println; use crate::{Num, NumCast, ToPrimitive}; @@ -2050,38 +2051,67 @@ macro_rules! float_impl_libm { }; } -fn integer_decode_f32(f: f32) -> (u64, i16, i8) { - let bits: u32 = f.to_bits(); - let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; - let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; - let mantissa = if exponent == 0 { - // Zeros and subnormals - (bits & 0x7fffff) << 1 - } else { - // Normals, infinities, and NaN - (bits & 0x7fffff) | 0x800000 - }; - // Exponent bias + mantissa shift - exponent -= 127 + 23; - (mantissa as u64, exponent, sign) -} +macro_rules! integer_decode { + ( + $func_name:ident, + $T:ty, + $sign_bit_index:expr, + $fraction_bits_start_index:expr, + $postshift_mask:expr, + $fraction_bits_mask:expr, + $exponent_trailing_bit_mask:expr, + $exponent_bias:expr + ) => { + fn $func_name(f: $T) -> (u64, i16, i8) { + println!(" sign_bit_index: {}", $sign_bit_index); + println!(" fraction_bits_start_index: {}", $fraction_bits_start_index); + println!(" postshift_mask: {:064b}", $postshift_mask); + println!(" fraction_bits_mask: {:064b}", $fraction_bits_mask); + println!("exponent_trailing_bit_mask: {:064b}", $exponent_trailing_bit_mask); + println!(" exponent_bias: {}", $exponent_bias); + + let bits = f.to_bits(); + println!(" bits: {:064b}", bits); + + let sign: i8 = if bits >> $sign_bit_index == 0 { 1 } else { -1 }; + let mantissa = if f == 0 as $T { + // Zeros and subnormals + (bits & $fraction_bits_mask) << 1 + } else { + // Normals, infinities, and NaN + (bits & $fraction_bits_mask) | $exponent_trailing_bit_mask + }; -fn integer_decode_f64(f: f64) -> (u64, i16, i8) { - let bits: u64 = f.to_bits(); - let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; - let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; - let mantissa = if exponent == 0 { - // Zeros and subnormals - (bits & 0xfffffffffffff) << 1 - } else { - // Normals, infinities, and NaN - (bits & 0xfffffffffffff) | 0x10000000000000 + let mut exponent: i16 = (bits >> $fraction_bits_start_index & $postshift_mask) as i16; + exponent -= $exponent_bias + $fraction_bits_start_index; + + (mantissa as u64, exponent, sign) + } }; - // Exponent bias + mantissa shift - exponent -= 1023 + 52; - (mantissa, exponent, sign) } +integer_decode!( + integer_decode_f32, + f32, + 31, + 23, + 0xff, + 0x7fffff, + 0x800000, + 127 +); + +integer_decode!( + integer_decode_f64, + f64, + 63, + 52, + 0x7ff, + 0xfffffffffffff_u64, + 0x10000000000000_u64, + 1023 +); + #[cfg(feature = "std")] float_impl_std!(f32 integer_decode_f32); #[cfg(feature = "std")] From 32710c8f38d8b7e40f1f943f1c5ba1b4a6236f3a Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Wed, 26 Jun 2024 21:02:36 -0400 Subject: [PATCH 09/11] Remove debug print statements --- src/float.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/float.rs b/src/float.rs index f730784a..ddaca308 100644 --- a/src/float.rs +++ b/src/float.rs @@ -4,7 +4,6 @@ use core::ops::{Add, Div, Neg}; use core::f32; use core::f64; -use std::println; use crate::{Num, NumCast, ToPrimitive}; @@ -2063,17 +2062,10 @@ macro_rules! integer_decode { $exponent_bias:expr ) => { fn $func_name(f: $T) -> (u64, i16, i8) { - println!(" sign_bit_index: {}", $sign_bit_index); - println!(" fraction_bits_start_index: {}", $fraction_bits_start_index); - println!(" postshift_mask: {:064b}", $postshift_mask); - println!(" fraction_bits_mask: {:064b}", $fraction_bits_mask); - println!("exponent_trailing_bit_mask: {:064b}", $exponent_trailing_bit_mask); - println!(" exponent_bias: {}", $exponent_bias); - let bits = f.to_bits(); - println!(" bits: {:064b}", bits); let sign: i8 = if bits >> $sign_bit_index == 0 { 1 } else { -1 }; + let mantissa = if f == 0 as $T { // Zeros and subnormals (bits & $fraction_bits_mask) << 1 From 5477176736e19219e16cf4c27f1aee8a21d17978 Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Thu, 27 Jun 2024 19:54:17 -0400 Subject: [PATCH 10/11] Rename and reorder macro parameters --- src/float.rs | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/float.rs b/src/float.rs index ddaca308..d726372d 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2053,29 +2053,29 @@ macro_rules! float_impl_libm { macro_rules! integer_decode { ( $func_name:ident, - $T:ty, - $sign_bit_index:expr, - $fraction_bits_start_index:expr, - $postshift_mask:expr, + $F:ty, + $size:literal, + $fraction_size:literal, + $exponent_bias:literal, $fraction_bits_mask:expr, - $exponent_trailing_bit_mask:expr, - $exponent_bias:expr + $exponent_least_signifigant_bit_mask:expr, + $postshift_exponent_bits_mask:expr ) => { - fn $func_name(f: $T) -> (u64, i16, i8) { + fn $func_name(f: $F) -> (u64, i16, i8) { let bits = f.to_bits(); - let sign: i8 = if bits >> $sign_bit_index == 0 { 1 } else { -1 }; + let sign: i8 = if bits >> $size - 1 == 0 { 1 } else { -1 }; - let mantissa = if f == 0 as $T { + let mantissa = if f == 0 as $F { // Zeros and subnormals (bits & $fraction_bits_mask) << 1 } else { // Normals, infinities, and NaN - (bits & $fraction_bits_mask) | $exponent_trailing_bit_mask + (bits & $fraction_bits_mask) | $exponent_least_signifigant_bit_mask }; - let mut exponent: i16 = (bits >> $fraction_bits_start_index & $postshift_mask) as i16; - exponent -= $exponent_bias + $fraction_bits_start_index; + let mut exponent: i16 = (bits >> $fraction_size & $postshift_exponent_bits_mask) as i16; + exponent -= $exponent_bias + $fraction_size; (mantissa as u64, exponent, sign) } @@ -2085,23 +2085,23 @@ macro_rules! integer_decode { integer_decode!( integer_decode_f32, f32, - 31, + 32, 23, - 0xff, - 0x7fffff, - 0x800000, - 127 + 127, + 0b0000_0000_0111_1111_1111_1111_1111_1111, + 0b0000_0000_1000_0000_0000_0000_0000_0000, + 0b0000_0000_0000_0000_0000_0000_1111_1111 ); integer_decode!( integer_decode_f64, f64, - 63, + 64, 52, - 0x7ff, - 0xfffffffffffff_u64, - 0x10000000000000_u64, - 1023 + 1023, + 0b0000_0000_0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111, + 0b0000_0000_0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000, + 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0111_1111_1111 ); #[cfg(feature = "std")] From 0a0a8e51bd2a0cb2d7a6be278302cd47daeabe23 Mon Sep 17 00:00:00 2001 From: Mathilda Grace Date: Fri, 28 Jun 2024 16:34:57 -0400 Subject: [PATCH 11/11] Fix bug causing subnormal test case to fail --- src/float.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/float.rs b/src/float.rs index d726372d..81b836c9 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2066,7 +2066,9 @@ macro_rules! integer_decode { let sign: i8 = if bits >> $size - 1 == 0 { 1 } else { -1 }; - let mantissa = if f == 0 as $F { + let mut exponent: i16 = (bits >> $fraction_size & $postshift_exponent_bits_mask) as i16; + + let mantissa = if exponent == 0 { // Zeros and subnormals (bits & $fraction_bits_mask) << 1 } else { @@ -2074,7 +2076,6 @@ macro_rules! integer_decode { (bits & $fraction_bits_mask) | $exponent_least_signifigant_bit_mask }; - let mut exponent: i16 = (bits >> $fraction_size & $postshift_exponent_bits_mask) as i16; exponent -= $exponent_bias + $fraction_size; (mantissa as u64, exponent, sign)