From fce07a82c632ec28ac02361b786e64bf56ec1739 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 16 Jun 2024 03:09:58 -0500 Subject: [PATCH 1/6] Extract repeated constants from `f32` and `f64` source This will make it easier to keep `f16` and `f128` consistent as their implementations get added. --- library/core/src/num/f32.rs | 45 ++++++++++--------- library/core/src/num/f64.rs | 51 +++++++++++----------- library/std/src/f32/tests.rs | 82 +++++++++++++++++++++++------------ library/std/src/f64/tests.rs | 83 ++++++++++++++++++++++++------------ 4 files changed, 160 insertions(+), 101 deletions(-) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 9d34d3da9e955..271965c28840a 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -490,6 +490,21 @@ impl f32 { #[stable(feature = "assoc_int_consts", since = "1.43.0")] pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32; + /// Sign bit + const SIGN_MASK: u32 = 0x8000_0000; + + /// Exponent mask + const EXP_MASK: u32 = 0x7f80_0000; + + /// Mantissa mask + const MAN_MASK: u32 = 0x007f_ffff; + + /// Minimum representable positive value (min subnormal) + const TINY_BITS: u32 = 0x1; + + /// Minimum representable negative value (min negative subnormal) + const NEG_TINY_BITS: u32 = Self::TINY_BITS | Self::SIGN_MASK; + /// Returns `true` if this value is NaN. /// /// ``` @@ -515,7 +530,7 @@ impl f32 { #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub(crate) const fn abs_private(self) -> f32 { // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. - unsafe { mem::transmute::(mem::transmute::(self) & 0x7fff_ffff) } + unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } } /// Returns `true` if this value is positive infinity or negative infinity, and @@ -682,12 +697,9 @@ impl f32 { // runtime-deviating logic which may or may not be acceptable. #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] const unsafe fn partial_classify(self) -> FpCategory { - const EXP_MASK: u32 = 0x7f800000; - const MAN_MASK: u32 = 0x007fffff; - // SAFETY: The caller is not asking questions for which this will tell lies. let b = unsafe { mem::transmute::(self) }; - match (b & MAN_MASK, b & EXP_MASK) { + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, @@ -699,12 +711,9 @@ impl f32 { // plus a transmute. We do not live in a just world, but we can make it more so. #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] const fn classify_bits(b: u32) -> FpCategory { - const EXP_MASK: u32 = 0x7f800000; - const MAN_MASK: u32 = 0x007fffff; - - match (b & MAN_MASK, b & EXP_MASK) { - (0, EXP_MASK) => FpCategory::Infinite, - (_, EXP_MASK) => FpCategory::Nan, + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + (0, Self::EXP_MASK) => FpCategory::Infinite, + (_, Self::EXP_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, @@ -789,17 +798,14 @@ impl f32 { pub const fn next_up(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. - const TINY_BITS: u32 = 0x1; // Smallest positive f32. - const CLEAR_SIGN_MASK: u32 = 0x7fff_ffff; - let bits = self.to_bits(); if self.is_nan() || bits == Self::INFINITY.to_bits() { return self; } - let abs = bits & CLEAR_SIGN_MASK; + let abs = bits & !Self::SIGN_MASK; let next_bits = if abs == 0 { - TINY_BITS + Self::TINY_BITS } else if bits == abs { bits + 1 } else { @@ -839,17 +845,14 @@ impl f32 { pub const fn next_down(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. - const NEG_TINY_BITS: u32 = 0x8000_0001; // Smallest (in magnitude) negative f32. - const CLEAR_SIGN_MASK: u32 = 0x7fff_ffff; - let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { return self; } - let abs = bits & CLEAR_SIGN_MASK; + let abs = bits & !Self::SIGN_MASK; let next_bits = if abs == 0 { - NEG_TINY_BITS + Self::NEG_TINY_BITS } else if bits == abs { bits - 1 } else { diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 95f021b2541ab..bccd39f605941 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -489,6 +489,21 @@ impl f64 { #[stable(feature = "assoc_int_consts", since = "1.43.0")] pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64; + /// Sign bit + const SIGN_MASK: u64 = 0x8000_0000_0000_0000; + + /// Exponent mask + const EXP_MASK: u64 = 0x7ff0_0000_0000_0000; + + /// Mantissa mask + const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff; + + /// Minimum representable positive value (min subnormal) + const TINY_BITS: u64 = 0x1; + + /// Minimum representable negative value (min negative subnormal) + const NEG_TINY_BITS: u64 = Self::TINY_BITS | Self::SIGN_MASK; + /// Returns `true` if this value is NaN. /// /// ``` @@ -514,9 +529,7 @@ impl f64 { #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] pub(crate) const fn abs_private(self) -> f64 { // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. - unsafe { - mem::transmute::(mem::transmute::(self) & 0x7fff_ffff_ffff_ffff) - } + unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } } /// Returns `true` if this value is positive infinity or negative infinity, and @@ -673,13 +686,10 @@ impl f64 { // and some normal floating point numbers truncated from an x87 FPU. #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] const unsafe fn partial_classify(self) -> FpCategory { - const EXP_MASK: u64 = 0x7ff0000000000000; - const MAN_MASK: u64 = 0x000fffffffffffff; - // SAFETY: The caller is not asking questions for which this will tell lies. let b = unsafe { mem::transmute::(self) }; - match (b & MAN_MASK, b & EXP_MASK) { - (0, EXP_MASK) => FpCategory::Infinite, + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + (0, Self::EXP_MASK) => FpCategory::Infinite, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, @@ -691,12 +701,9 @@ impl f64 { // plus a transmute. We do not live in a just world, but we can make it more so. #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] const fn classify_bits(b: u64) -> FpCategory { - const EXP_MASK: u64 = 0x7ff0000000000000; - const MAN_MASK: u64 = 0x000fffffffffffff; - - match (b & MAN_MASK, b & EXP_MASK) { - (0, EXP_MASK) => FpCategory::Infinite, - (_, EXP_MASK) => FpCategory::Nan, + match (b & Self::MAN_MASK, b & Self::EXP_MASK) { + (0, Self::EXP_MASK) => FpCategory::Infinite, + (_, Self::EXP_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, @@ -756,7 +763,7 @@ impl f64 { // IEEE754 says: isSignMinus(x) is true if and only if x has negative sign. isSignMinus // applies to zeros and NaNs as well. // SAFETY: This is just transmuting to get the sign bit, it's fine. - unsafe { mem::transmute::(self) & 0x8000_0000_0000_0000 != 0 } + unsafe { mem::transmute::(self) & Self::SIGN_MASK != 0 } } #[must_use] @@ -799,17 +806,14 @@ impl f64 { pub const fn next_up(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. - const TINY_BITS: u64 = 0x1; // Smallest positive f64. - const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; - let bits = self.to_bits(); if self.is_nan() || bits == Self::INFINITY.to_bits() { return self; } - let abs = bits & CLEAR_SIGN_MASK; + let abs = bits & !Self::SIGN_MASK; let next_bits = if abs == 0 { - TINY_BITS + Self::TINY_BITS } else if bits == abs { bits + 1 } else { @@ -849,17 +853,14 @@ impl f64 { pub const fn next_down(self) -> Self { // We must use strictly integer arithmetic to prevent denormals from // flushing to zero after an arithmetic operation on some platforms. - const NEG_TINY_BITS: u64 = 0x8000_0000_0000_0001; // Smallest (in magnitude) negative f64. - const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; - let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { return self; } - let abs = bits & CLEAR_SIGN_MASK; + let abs = bits & !Self::SIGN_MASK; let next_bits = if abs == 0 { - NEG_TINY_BITS + Self::NEG_TINY_BITS } else if bits == abs { bits - 1 } else { diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 9ca4e8f2f45fe..63e65698374c8 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -2,6 +2,45 @@ use crate::f32::consts; use crate::num::FpCategory as Fp; use crate::num::*; +/// Smallest number +#[allow(dead_code)] // unused on x86 +const TINY_BITS: u32 = 0x1; + +/// Next smallest number +#[allow(dead_code)] // unused on x86 +const TINY_UP_BITS: u32 = 0x2; + +/// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 +#[allow(dead_code)] // unused on x86 +const MAX_DOWN_BITS: u32 = 0x7f7f_fffe; + +/// Zeroed exponent, full significant +#[allow(dead_code)] // unused on x86 +const LARGEST_SUBNORMAL_BITS: u32 = 0x007f_ffff; + +/// Exponent = 0b1, zeroed significand +#[allow(dead_code)] // unused on x86 +const SMALLEST_NORMAL_BITS: u32 = 0x0080_0000; + +/// First pattern over the mantissa +#[allow(dead_code)] // unused on x86 +const NAN_MASK1: u32 = 0x002a_aaaa; + +/// Second pattern over the mantissa +#[allow(dead_code)] // unused on x86 +const NAN_MASK2: u32 = 0x0055_5555; + +#[allow(unused_macros)] +macro_rules! assert_f32_biteq { + ($left : expr, $right : expr) => { + let l: &f32 = &$left; + let r: &f32 = &$right; + let lb = l.to_bits(); + let rb = r.to_bits(); + assert_eq!(lb, rb, "float {l} ({lb:#010x}) is not bitequal to {r} ({rb:#010x})"); + }; +} + #[test] fn test_num_f32() { test_num(10f32, 2f32); @@ -315,27 +354,16 @@ fn test_is_sign_negative() { assert!((-f32::NAN).is_sign_negative()); } -#[allow(unused_macros)] -macro_rules! assert_f32_biteq { - ($left : expr, $right : expr) => { - let l: &f32 = &$left; - let r: &f32 = &$right; - let lb = l.to_bits(); - let rb = r.to_bits(); - assert_eq!(lb, rb, "float {} ({:#x}) is not equal to {} ({:#x})", *l, lb, *r, rb); - }; -} - // Ignore test on x87 floating point, these platforms do not guarantee NaN // payloads are preserved and flush denormals to zero, failing the tests. #[cfg(not(target_arch = "x86"))] #[test] fn test_next_up() { - let tiny = f32::from_bits(1); - let tiny_up = f32::from_bits(2); - let max_down = f32::from_bits(0x7f7f_fffe); - let largest_subnormal = f32::from_bits(0x007f_ffff); - let smallest_normal = f32::from_bits(0x0080_0000); + let tiny = f32::from_bits(TINY_BITS); + let tiny_up = f32::from_bits(TINY_UP_BITS); + let max_down = f32::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f32::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f32::from_bits(SMALLEST_NORMAL_BITS); assert_f32_biteq!(f32::NEG_INFINITY.next_up(), f32::MIN); assert_f32_biteq!(f32::MIN.next_up(), -max_down); assert_f32_biteq!((-1.0 - f32::EPSILON).next_up(), -1.0); @@ -352,8 +380,8 @@ fn test_next_up() { // Check that NaNs roundtrip. let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK1); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK2); assert_f32_biteq!(nan0.next_up(), nan0); assert_f32_biteq!(nan1.next_up(), nan1); assert_f32_biteq!(nan2.next_up(), nan2); @@ -364,11 +392,11 @@ fn test_next_up() { #[cfg(not(target_arch = "x86"))] #[test] fn test_next_down() { - let tiny = f32::from_bits(1); - let tiny_up = f32::from_bits(2); - let max_down = f32::from_bits(0x7f7f_fffe); - let largest_subnormal = f32::from_bits(0x007f_ffff); - let smallest_normal = f32::from_bits(0x0080_0000); + let tiny = f32::from_bits(TINY_BITS); + let tiny_up = f32::from_bits(TINY_UP_BITS); + let max_down = f32::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f32::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f32::from_bits(SMALLEST_NORMAL_BITS); assert_f32_biteq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); assert_f32_biteq!(f32::MIN.next_down(), f32::NEG_INFINITY); assert_f32_biteq!((-max_down).next_down(), f32::MIN); @@ -386,8 +414,8 @@ fn test_next_down() { // Check that NaNs roundtrip. let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ 0x0055_5555); + let nan1 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK1); + let nan2 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK2); assert_f32_biteq!(nan0.next_down(), nan0); assert_f32_biteq!(nan1.next_down(), nan1); assert_f32_biteq!(nan2.next_down(), nan2); @@ -734,8 +762,8 @@ fn test_float_bits_conv() { // Check that NaNs roundtrip their bits regardless of signaling-ness // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - let masked_nan1 = f32::NAN.to_bits() ^ 0x002A_AAAA; - let masked_nan2 = f32::NAN.to_bits() ^ 0x0055_5555; + let masked_nan1 = f32::NAN.to_bits() ^ NAN_MASK1; + let masked_nan2 = f32::NAN.to_bits() ^ NAN_MASK2; assert!(f32::from_bits(masked_nan1).is_nan()); assert!(f32::from_bits(masked_nan2).is_nan()); diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index f88d01593b5e4..d9e17fd601d2d 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -2,6 +2,45 @@ use crate::f64::consts; use crate::num::FpCategory as Fp; use crate::num::*; +/// Smallest number +#[allow(dead_code)] // unused on x86 +const TINY_BITS: u64 = 0x1; + +/// Next smallest number +#[allow(dead_code)] // unused on x86 +const TINY_UP_BITS: u64 = 0x2; + +/// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 +#[allow(dead_code)] // unused on x86 +const MAX_DOWN_BITS: u64 = 0x7fef_ffff_ffff_fffe; + +/// Zeroed exponent, full significant +#[allow(dead_code)] // unused on x86 +const LARGEST_SUBNORMAL_BITS: u64 = 0x000f_ffff_ffff_ffff; + +/// Exponent = 0b1, zeroed significand +#[allow(dead_code)] // unused on x86 +const SMALLEST_NORMAL_BITS: u64 = 0x0010_0000_0000_0000; + +/// First pattern over the mantissa +#[allow(dead_code)] // unused on x86 +const NAN_MASK1: u64 = 0x000a_aaaa_aaaa_aaaa; + +/// Second pattern over the mantissa +#[allow(dead_code)] // unused on x86 +const NAN_MASK2: u64 = 0x0005_5555_5555_5555; + +#[allow(unused_macros)] +macro_rules! assert_f64_biteq { + ($left : expr, $right : expr) => { + let l: &f64 = &$left; + let r: &f64 = &$right; + let lb = l.to_bits(); + let rb = r.to_bits(); + assert_eq!(lb, rb, "float {l} ({lb:#018x}) is not bitequal to {r} ({rb:#018x})"); + }; +} + #[test] fn test_num_f64() { test_num(10f64, 2f64); @@ -305,27 +344,16 @@ fn test_is_sign_negative() { assert!((-f64::NAN).is_sign_negative()); } -#[allow(unused_macros)] -macro_rules! assert_f64_biteq { - ($left : expr, $right : expr) => { - let l: &f64 = &$left; - let r: &f64 = &$right; - let lb = l.to_bits(); - let rb = r.to_bits(); - assert_eq!(lb, rb, "float {} ({:#x}) is not equal to {} ({:#x})", *l, lb, *r, rb); - }; -} - // Ignore test on x87 floating point, these platforms do not guarantee NaN // payloads are preserved and flush denormals to zero, failing the tests. #[cfg(not(target_arch = "x86"))] #[test] fn test_next_up() { - let tiny = f64::from_bits(1); - let tiny_up = f64::from_bits(2); - let max_down = f64::from_bits(0x7fef_ffff_ffff_fffe); - let largest_subnormal = f64::from_bits(0x000f_ffff_ffff_ffff); - let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); + let tiny = f64::from_bits(TINY_BITS); + let tiny_up = f64::from_bits(TINY_UP_BITS); + let max_down = f64::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f64::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f64::from_bits(SMALLEST_NORMAL_BITS); assert_f64_biteq!(f64::NEG_INFINITY.next_up(), f64::MIN); assert_f64_biteq!(f64::MIN.next_up(), -max_down); assert_f64_biteq!((-1.0 - f64::EPSILON).next_up(), -1.0); @@ -341,8 +369,8 @@ fn test_next_up() { assert_f64_biteq!(f64::INFINITY.next_up(), f64::INFINITY); let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK1); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK2); assert_f64_biteq!(nan0.next_up(), nan0); assert_f64_biteq!(nan1.next_up(), nan1); assert_f64_biteq!(nan2.next_up(), nan2); @@ -353,11 +381,11 @@ fn test_next_up() { #[cfg(not(target_arch = "x86"))] #[test] fn test_next_down() { - let tiny = f64::from_bits(1); - let tiny_up = f64::from_bits(2); - let max_down = f64::from_bits(0x7fef_ffff_ffff_fffe); - let largest_subnormal = f64::from_bits(0x000f_ffff_ffff_ffff); - let smallest_normal = f64::from_bits(0x0010_0000_0000_0000); + let tiny = f64::from_bits(TINY_BITS); + let tiny_up = f64::from_bits(TINY_UP_BITS); + let max_down = f64::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f64::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f64::from_bits(SMALLEST_NORMAL_BITS); assert_f64_biteq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); assert_f64_biteq!(f64::MIN.next_down(), f64::NEG_INFINITY); assert_f64_biteq!((-max_down).next_down(), f64::MIN); @@ -374,8 +402,8 @@ fn test_next_down() { assert_f64_biteq!(f64::INFINITY.next_down(), f64::MAX); let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ 0x000a_aaaa_aaaa_aaaa); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ 0x0005_5555_5555_5555); + let nan1 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK1); + let nan2 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK2); assert_f64_biteq!(nan0.next_down(), nan0); assert_f64_biteq!(nan1.next_down(), nan1); assert_f64_biteq!(nan2.next_down(), nan2); @@ -715,9 +743,8 @@ fn test_float_bits_conv() { assert_approx_eq!(f64::from_bits(0xc02c800000000000), -14.25); // Check that NaNs roundtrip their bits regardless of signaling-ness - // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits - let masked_nan1 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; - let masked_nan2 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; + let masked_nan1 = f64::NAN.to_bits() ^ NAN_MASK1; + let masked_nan2 = f64::NAN.to_bits() ^ NAN_MASK2; assert!(f64::from_bits(masked_nan1).is_nan()); assert!(f64::from_bits(masked_nan2).is_nan()); From 0314fe62bd178200a620b90b59becd735d6c2e66 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 21 Jun 2024 03:30:15 -0400 Subject: [PATCH 2/6] Reword docs for `f32` and `f64` Better explain the reasoning for the `next_up`/`next_down` integer implementation, as requested by Ralf. --- library/core/src/num/f32.rs | 10 ++++++---- library/core/src/num/f64.rs | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 271965c28840a..b9c84a66ed138 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -796,8 +796,9 @@ impl f32 { #[unstable(feature = "float_next_up_down", issue = "91399")] #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_up(self) -> Self { - // We must use strictly integer arithmetic to prevent denormals from - // flushing to zero after an arithmetic operation on some platforms. + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. let bits = self.to_bits(); if self.is_nan() || bits == Self::INFINITY.to_bits() { return self; @@ -843,8 +844,9 @@ impl f32 { #[unstable(feature = "float_next_up_down", issue = "91399")] #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_down(self) -> Self { - // We must use strictly integer arithmetic to prevent denormals from - // flushing to zero after an arithmetic operation on some platforms. + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { return self; diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index bccd39f605941..f8e4555fc44f2 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -804,8 +804,9 @@ impl f64 { #[unstable(feature = "float_next_up_down", issue = "91399")] #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_up(self) -> Self { - // We must use strictly integer arithmetic to prevent denormals from - // flushing to zero after an arithmetic operation on some platforms. + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. let bits = self.to_bits(); if self.is_nan() || bits == Self::INFINITY.to_bits() { return self; @@ -851,8 +852,9 @@ impl f64 { #[unstable(feature = "float_next_up_down", issue = "91399")] #[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")] pub const fn next_down(self) -> Self { - // We must use strictly integer arithmetic to prevent denormals from - // flushing to zero after an arithmetic operation on some platforms. + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { return self; From 99ed3d3e99da5bf7c1fc0df49425e2dc4d0ca2a9 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 20 Jun 2024 22:05:07 -0400 Subject: [PATCH 3/6] Add build.rs config for reliable `f16` and `f128` There are some complexities about what platforms we can test f16 and f128 on. Put this in build.rs so we have an easy way to configure tests with a single attribute, and keep it up to date. --- library/std/build.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/library/std/build.rs b/library/std/build.rs index 7d975df545ecf..55388648a14b9 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -7,6 +7,10 @@ fn main() { let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").expect("CARGO_CFG_TARGET_VENDOR was not set"); let target_env = env::var("CARGO_CFG_TARGET_ENV").expect("CARGO_CFG_TARGET_ENV was not set"); + let target_pointer_width: u32 = env::var("CARGO_CFG_TARGET_POINTER_WIDTH") + .expect("CARGO_CFG_TARGET_POINTER_WIDTH was not set") + .parse() + .unwrap(); println!("cargo:rustc-check-cfg=cfg(netbsd10)"); if target_os == "netbsd" && env::var("RUSTC_STD_NETBSD10").is_ok() { @@ -70,4 +74,62 @@ fn main() { println!("cargo:rustc-cfg=backtrace_in_libstd"); println!("cargo:rustc-env=STD_ENV_ARCH={}", env::var("CARGO_CFG_TARGET_ARCH").unwrap()); + + // Emit these on platforms that have no known ABI bugs, LLVM selection bugs, lowering bugs, + // missing symbols, or other problems, to determine when tests get run. + // If more broken platforms are found, please update the tracking issue at + // + // + // Some of these match arms are redundant; the goal is to separate reasons that the type is + // unreliable, even when multiple reasons might fail the same platform. + println!("cargo:rustc-check-cfg=cfg(reliable_f16)"); + println!("cargo:rustc-check-cfg=cfg(reliable_f128)"); + + let has_reliable_f16 = match (target_arch.as_str(), target_os.as_str()) { + // Selection failure until recent LLVM + // FIXME(llvm19): can probably be removed at the version bump + ("loongarch64", _) => false, + // Selection failure + ("s390x", _) => false, + // Unsupported + ("arm64ec", _) => false, + // MinGW ABI bugs + ("x86", "windows") => false, + // x86 has ABI bugs that show up with optimizations. This should be partially fixed with + // the compiler-builtins update. + ("x86" | "x86_64", _) => false, + // Missing `__gnu_h2f_ieee` and `__gnu_f2h_ieee` + ("powerpc" | "powerpc64" | "powerpc64le", _) => false, + // Missing `__extendhfsf` and `__truncsfhf` + ("riscv32" | "riscv64", _) => false, + // Most OSs are missing `__extendhfsf` and `__truncsfhf` + (_, "linux" | "macos") => true, + // Almost all OSs besides Linux and MacOS are missing symbols until compiler-builtins can + // be updated. will get some of these, the + // next CB update should get the rest. + _ => false, + }; + + let has_reliable_f128 = match (target_arch.as_str(), target_os.as_str()) { + // Unsupported + ("arm64ec", _) => false, + // ABI and precision bugs + // + ("powerpc" | "powerpc64", _) => false, + // Selection bug + ("nvptx64", _) => false, + // ABI unsupported + ("sparc", _) => false, + // 64-bit Linux is about the only platform to have f128 symbols by default + (_, "linux") if target_pointer_width == 64 => true, + // Same as for f16, except MacOS is also missing f128 symbols. + _ => false, + }; + + if has_reliable_f16 { + println!("cargo:rustc-cfg=reliable_f16"); + } + if has_reliable_f128 { + println!("cargo:rustc-cfg=reliable_f128"); + } } From 0eee0557d076e24c716740c168428f6d12cdf566 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 18 Jun 2024 18:22:14 -0500 Subject: [PATCH 4/6] Add doctests to existing `f16` and `f128` functions The symbols that these tests rely on are not available on all platforms and some ABIs are buggy, tests that rely on external functions are configured to only run on x86 (`f128`) or aarch64 (`f16`). --- library/core/src/num/f128.rs | 36 ++++++++++++++++++++++++++++++++++-- library/core/src/num/f16.rs | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 129f62fb43d1a..490d7bd504dfe 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -221,8 +221,22 @@ impl f128 { pub const MAX_10_EXP: i32 = 4_932; /// Returns `true` if this value is NaN. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `unordtf2` is available + /// # #[cfg(target_arch = "x86_64", target_os = "linux")] { + /// + /// let nan = f128::NAN; + /// let f = 7.0_f128; + /// + /// assert!(nan.is_nan()); + /// assert!(!f.is_nan()); + /// # } + /// ``` #[inline] #[must_use] + #[cfg(not(bootstrap))] #[unstable(feature = "f128", issue = "116909")] #[allow(clippy::eq_op)] // > if you intended to check if the operand is NaN, use `.is_nan()` instead :) pub const fn is_nan(self) -> bool { @@ -234,7 +248,7 @@ impl f128 { /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that /// the bit pattern of NaNs are conserved over arithmetic operations, the result of /// `is_sign_positive` on a NaN might produce an unexpected result in some cases. - /// See [explanation of NaN as a special value](f32) for more info. + /// See [explanation of NaN as a special value](f128) for more info. /// /// ``` /// #![feature(f128)] @@ -257,7 +271,7 @@ impl f128 { /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that /// the bit pattern of NaNs are conserved over arithmetic operations, the result of /// `is_sign_negative` on a NaN might produce an unexpected result in some cases. - /// See [explanation of NaN as a special value](f32) for more info. + /// See [explanation of NaN as a special value](f128) for more info. /// /// ``` /// #![feature(f128)] @@ -287,6 +301,14 @@ impl f128 { /// /// Note that this function is distinct from `as` casting, which attempts to /// preserve the *numeric* value, and not the bitwise value. + /// + /// ``` + /// #![feature(f128)] + /// + /// # // FIXME(f16_f128): enable this once const casting works + /// # // assert_ne!((1f128).to_bits(), 1f128 as u128); // to_bits() is not casting! + /// assert_eq!((12.5f128).to_bits(), 0x40029000000000000000000000000000); + /// ``` #[inline] #[unstable(feature = "f128", issue = "116909")] #[must_use = "this returns the result of the operation, without modifying the original"] @@ -326,6 +348,16 @@ impl f128 { /// /// Note that this function is distinct from `as` casting, which attempts to /// preserve the *numeric* value, and not the bitwise value. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let v = f128::from_bits(0x40029000000000000000000000000000); + /// assert_eq!(v, 12.5); + /// # } + /// ``` #[inline] #[must_use] #[unstable(feature = "f128", issue = "116909")] diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 7a488cd6bf6fa..4b0c25fcce967 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -216,8 +216,21 @@ impl f16 { pub const MAX_10_EXP: i32 = 4; /// Returns `true` if this value is NaN. + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "x86_64")] { // FIXME(f16_f128): remove when ABI bugs are fixed + /// + /// let nan = f16::NAN; + /// let f = 7.0_f16; + /// + /// assert!(nan.is_nan()); + /// assert!(!f.is_nan()); + /// # } + /// ``` #[inline] #[must_use] + #[cfg(not(bootstrap))] #[unstable(feature = "f16", issue = "116909")] #[allow(clippy::eq_op)] // > if you intended to check if the operand is NaN, use `.is_nan()` instead :) pub const fn is_nan(self) -> bool { @@ -229,7 +242,7 @@ impl f16 { /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that /// the bit pattern of NaNs are conserved over arithmetic operations, the result of /// `is_sign_positive` on a NaN might produce an unexpected result in some cases. - /// See [explanation of NaN as a special value](f32) for more info. + /// See [explanation of NaN as a special value](f16) for more info. /// /// ``` /// #![feature(f16)] @@ -252,7 +265,7 @@ impl f16 { /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that /// the bit pattern of NaNs are conserved over arithmetic operations, the result of /// `is_sign_negative` on a NaN might produce an unexpected result in some cases. - /// See [explanation of NaN as a special value](f32) for more info. + /// See [explanation of NaN as a special value](f16) for more info. /// /// ``` /// #![feature(f16)] @@ -282,6 +295,16 @@ impl f16 { /// /// Note that this function is distinct from `as` casting, which attempts to /// preserve the *numeric* value, and not the bitwise value. + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// # // FIXME(f16_f128): enable this once const casting works + /// # // assert_ne!((1f16).to_bits(), 1f16 as u128); // to_bits() is not casting! + /// assert_eq!((12.5f16).to_bits(), 0x4a40); + /// # } + /// ``` #[inline] #[unstable(feature = "f16", issue = "116909")] #[must_use = "this returns the result of the operation, without modifying the original"] @@ -321,6 +344,15 @@ impl f16 { /// /// Note that this function is distinct from `as` casting, which attempts to /// preserve the *numeric* value, and not the bitwise value. + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let v = f16::from_bits(0x4a40); + /// assert_eq!(v, 12.5); + /// # } + /// ``` #[inline] #[must_use] #[unstable(feature = "f16", issue = "116909")] From 6e2d934a881cc79c14cf7b92e7f57a13426b7c26 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 18 Jun 2024 18:25:07 -0500 Subject: [PATCH 5/6] Add more `f16` and `f128` library functions and constants This adds everything that was directly or transitively blocked on const arithmetic for these types, which was recently merged. Since const arithmetic is recent, most of these need to be gated by `bootstrap`. Anything that relies on intrinsics that are still missing is excluded. --- library/core/src/num/f128.rs | 640 ++++++++++++++++++++++++++++++++++- library/core/src/num/f16.rs | 612 ++++++++++++++++++++++++++++++++- library/std/src/f128.rs | 30 ++ library/std/src/f16.rs | 29 ++ 4 files changed, 1309 insertions(+), 2 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 490d7bd504dfe..58ed98c888cc6 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -11,6 +11,7 @@ #![unstable(feature = "f128", issue = "116909")] +use crate::convert::FloatToInt; use crate::mem; /// Basic mathematical constants. @@ -220,12 +221,50 @@ impl f128 { #[unstable(feature = "f128", issue = "116909")] pub const MAX_10_EXP: i32 = 4_932; + /// Not a Number (NaN). + /// + /// Note that IEEE 754 doesn't define just a single NaN value; + /// a plethora of bit patterns are considered to be NaN. + /// Furthermore, the standard makes a difference + /// between a "signaling" and a "quiet" NaN, + /// and allows inspecting its "payload" (the unspecified bits in the bit pattern). + /// This constant isn't guaranteed to equal to any specific NaN bitpattern, + /// and the stability of its representation over Rust versions + /// and target platforms isn't guaranteed. + #[cfg(not(bootstrap))] + #[allow(clippy::eq_op)] + #[rustc_diagnostic_item = "f128_nan"] + #[unstable(feature = "f128", issue = "116909")] + pub const NAN: f128 = 0.0_f128 / 0.0_f128; + + /// Infinity (∞). + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + pub const INFINITY: f128 = 1.0_f128 / 0.0_f128; + + /// Negative infinity (−∞). + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + pub const NEG_INFINITY: f128 = -1.0_f128 / 0.0_f128; + + /// Sign bit + #[cfg(not(bootstrap))] + pub(crate) const SIGN_MASK: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; + + /// Minimum representable positive value (min subnormal) + #[cfg(not(bootstrap))] + const TINY_BITS: u128 = 0x1; + + /// Minimum representable negative value (min negative subnormal) + #[cfg(not(bootstrap))] + const NEG_TINY_BITS: u128 = Self::TINY_BITS | Self::SIGN_MASK; + /// Returns `true` if this value is NaN. /// /// ``` /// #![feature(f128)] /// # // FIXME(f16_f128): remove when `unordtf2` is available - /// # #[cfg(target_arch = "x86_64", target_os = "linux")] { + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { /// /// let nan = f128::NAN; /// let f = 7.0_f128; @@ -243,6 +282,78 @@ impl f128 { self != self } + // FIXME(#50145): `abs` is publicly unavailable in core due to + // concerns about portability, so this implementation is for + // private use internally. + #[inline] + #[cfg(not(bootstrap))] + #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] + pub(crate) const fn abs_private(self) -> f128 { + // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. + unsafe { + mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) + } + } + + /// Returns `true` if this value is positive infinity or negative infinity, and + /// `false` otherwise. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let f = 7.0f128; + /// let inf = f128::INFINITY; + /// let neg_inf = f128::NEG_INFINITY; + /// let nan = f128::NAN; + /// + /// assert!(!f.is_infinite()); + /// assert!(!nan.is_infinite()); + /// + /// assert!(inf.is_infinite()); + /// assert!(neg_inf.is_infinite()); + /// # } + /// ``` + #[inline] + #[must_use] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] + pub const fn is_infinite(self) -> bool { + (self == f128::INFINITY) | (self == f128::NEG_INFINITY) + } + + /// Returns `true` if this number is neither infinite nor NaN. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `lttf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let f = 7.0f128; + /// let inf: f128 = f128::INFINITY; + /// let neg_inf: f128 = f128::NEG_INFINITY; + /// let nan: f128 = f128::NAN; + /// + /// assert!(f.is_finite()); + /// + /// assert!(!nan.is_finite()); + /// assert!(!inf.is_finite()); + /// assert!(!neg_inf.is_finite()); + /// # } + /// ``` + #[inline] + #[must_use] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] + pub const fn is_finite(self) -> bool { + // There's no need to handle NaN separately: if self is NaN, + // the comparison is not true, exactly as desired. + self.abs_private() < Self::INFINITY + } + /// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with /// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that @@ -292,6 +403,222 @@ impl f128 { (self.to_bits() & (1 << 127)) != 0 } + /// Returns the least number greater than `self`. + /// + /// Let `TINY` be the smallest representable positive `f128`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`NEG_INFINITY`], this returns [`MIN`]; + /// - if `self` is `-TINY`, this returns -0.0; + /// - if `self` is -0.0 or +0.0, this returns `TINY`; + /// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; + /// - otherwise the unique least value greater than `self` is returned. + /// + /// The identity `x.next_up() == -(-x).next_down()` holds for all non-NaN `x`. When `x` + /// is finite `x == x.next_up().next_down()` also holds. + /// + /// ```rust + /// #![feature(f128)] + /// #![feature(float_next_up_down)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// // f128::EPSILON is the difference between 1.0 and the next number up. + /// assert_eq!(1.0f128.next_up(), 1.0 + f128::EPSILON); + /// // But not for most numbers. + /// assert!(0.1f128.next_up() < 0.1 + f128::EPSILON); + /// assert_eq!(4611686018427387904f128.next_up(), 4611686018427387904.000000000000001); + /// # } + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + // #[unstable(feature = "float_next_up_down", issue = "91399")] + pub fn next_up(self) -> Self { + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. + let bits = self.to_bits(); + if self.is_nan() || bits == Self::INFINITY.to_bits() { + return self; + } + + let abs = bits & !Self::SIGN_MASK; + let next_bits = if abs == 0 { + Self::TINY_BITS + } else if bits == abs { + bits + 1 + } else { + bits - 1 + }; + Self::from_bits(next_bits) + } + + /// Returns the greatest number less than `self`. + /// + /// Let `TINY` be the smallest representable positive `f128`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`INFINITY`], this returns [`MAX`]; + /// - if `self` is `TINY`, this returns 0.0; + /// - if `self` is -0.0 or +0.0, this returns `-TINY`; + /// - if `self` is [`MIN`] or [`NEG_INFINITY`], this returns [`NEG_INFINITY`]; + /// - otherwise the unique greatest value less than `self` is returned. + /// + /// The identity `x.next_down() == -(-x).next_up()` holds for all non-NaN `x`. When `x` + /// is finite `x == x.next_down().next_up()` also holds. + /// + /// ```rust + /// #![feature(f128)] + /// #![feature(float_next_up_down)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let x = 1.0f128; + /// // Clamp value into range [0, 1). + /// let clamped = x.clamp(0.0, 1.0f128.next_down()); + /// assert!(clamped < 1.0); + /// assert_eq!(clamped.next_up(), 1.0); + /// # } + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + // #[unstable(feature = "float_next_up_down", issue = "91399")] + pub fn next_down(self) -> Self { + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. + let bits = self.to_bits(); + if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { + return self; + } + + let abs = bits & !Self::SIGN_MASK; + let next_bits = if abs == 0 { + Self::NEG_TINY_BITS + } else if bits == abs { + bits - 1 + } else { + bits + 1 + }; + Self::from_bits(next_bits) + } + + /// Takes the reciprocal (inverse) of a number, `1/x`. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let x = 2.0_f128; + /// let abs_difference = (x.recip() - (1.0 / x)).abs(); + /// + /// assert!(abs_difference <= f128::EPSILON); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn recip(self) -> Self { + 1.0 / self + } + + /// Converts radians to degrees. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let angle = std::f128::consts::PI; + /// + /// let abs_difference = (angle.to_degrees() - 180.0).abs(); + /// assert!(abs_difference <= f128::EPSILON); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_degrees(self) -> Self { + // Use a literal for better precision. + const PIS_IN_180: f128 = 57.2957795130823208767981548141051703324054724665643215491602_f128; + self * PIS_IN_180 + } + + /// Converts degrees to radians. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let angle = 180.0f128; + /// + /// let abs_difference = (angle.to_radians() - std::f128::consts::PI).abs(); + /// + /// assert!(abs_difference <= 1e-30); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_radians(self) -> f128 { + // Use a literal for better precision. + const RADS_PER_DEG: f128 = + 0.0174532925199432957692369076848861271344287188854172545609719_f128; + self * RADS_PER_DEG + } + + /// Rounds toward zero and converts to any primitive integer type, + /// assuming that the value is finite and fits in that type. + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `float*itf` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let value = 4.6_f128; + /// let rounded = unsafe { value.to_int_unchecked::() }; + /// assert_eq!(rounded, 4); + /// + /// let value = -128.9_f128; + /// let rounded = unsafe { value.to_int_unchecked::() }; + /// assert_eq!(rounded, i8::MIN); + /// # } + /// ``` + /// + /// # Safety + /// + /// The value must: + /// + /// * Not be `NaN` + /// * Not be infinite + /// * Be representable in the return type `Int`, after truncating off its fractional part + #[inline] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub unsafe fn to_int_unchecked(self) -> Int + where + Self: FloatToInt, + { + // SAFETY: the caller must uphold the safety contract for + // `FloatToInt::to_int_unchecked`. + unsafe { FloatToInt::::to_int_unchecked(self) } + } + /// Raw transmutation to `u128`. /// /// This is currently identical to `transmute::(self)` on all platforms. @@ -367,4 +694,315 @@ impl f128 { // Stability concerns. unsafe { mem::transmute(v) } } + + /// Return the memory representation of this floating point number as a byte array in + /// big-endian (network) byte order. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// + /// let bytes = 12.5f128.to_be_bytes(); + /// assert_eq!( + /// bytes, + /// [0x40, 0x02, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + /// ); + /// ``` + #[inline] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_be_bytes(self) -> [u8; 16] { + self.to_bits().to_be_bytes() + } + + /// Return the memory representation of this floating point number as a byte array in + /// little-endian byte order. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// + /// let bytes = 12.5f128.to_le_bytes(); + /// assert_eq!( + /// bytes, + /// [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x02, 0x40] + /// ); + /// ``` + #[inline] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_le_bytes(self) -> [u8; 16] { + self.to_bits().to_le_bytes() + } + + /// Return the memory representation of this floating point number as a byte array in + /// native byte order. + /// + /// As the target platform's native endianness is used, portable code + /// should use [`to_be_bytes`] or [`to_le_bytes`], as appropriate, instead. + /// + /// [`to_be_bytes`]: f128::to_be_bytes + /// [`to_le_bytes`]: f128::to_le_bytes + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// + /// let bytes = 12.5f128.to_ne_bytes(); + /// assert_eq!( + /// bytes, + /// if cfg!(target_endian = "big") { + /// [0x40, 0x02, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + /// } else { + /// [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x02, 0x40] + /// } + /// ); + /// ``` + #[inline] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_ne_bytes(self) -> [u8; 16] { + self.to_bits().to_ne_bytes() + } + + /// Create a floating point value from its representation as a byte array in big endian. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let value = f128::from_be_bytes( + /// [0x40, 0x02, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + /// ); + /// assert_eq!(value, 12.5); + /// # } + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "f128", issue = "116909")] + pub fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self::from_bits(u128::from_be_bytes(bytes)) + } + + /// Create a floating point value from its representation as a byte array in little endian. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let value = f128::from_le_bytes( + /// [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x02, 0x40] + /// ); + /// assert_eq!(value, 12.5); + /// # } + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "f128", issue = "116909")] + pub fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self::from_bits(u128::from_le_bytes(bytes)) + } + + /// Create a floating point value from its representation as a byte array in native endian. + /// + /// As the target platform's native endianness is used, portable code + /// likely wants to use [`from_be_bytes`] or [`from_le_bytes`], as + /// appropriate instead. + /// + /// [`from_be_bytes`]: f128::from_be_bytes + /// [`from_le_bytes`]: f128::from_le_bytes + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `eqtf2` is available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let value = f128::from_ne_bytes(if cfg!(target_endian = "big") { + /// [0x40, 0x02, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + /// } else { + /// [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x02, 0x40] + /// }); + /// assert_eq!(value, 12.5); + /// # } + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "f128", issue = "116909")] + pub fn from_ne_bytes(bytes: [u8; 16]) -> Self { + Self::from_bits(u128::from_ne_bytes(bytes)) + } + + /// Return the ordering between `self` and `other`. + /// + /// Unlike the standard partial comparison between floating point numbers, + /// this comparison always produces an ordering in accordance to + /// the `totalOrder` predicate as defined in the IEEE 754 (2008 revision) + /// floating point standard. The values are ordered in the following sequence: + /// + /// - negative quiet NaN + /// - negative signaling NaN + /// - negative infinity + /// - negative numbers + /// - negative subnormal numbers + /// - negative zero + /// - positive zero + /// - positive subnormal numbers + /// - positive numbers + /// - positive infinity + /// - positive signaling NaN + /// - positive quiet NaN. + /// + /// The ordering established by this function does not always agree with the + /// [`PartialOrd`] and [`PartialEq`] implementations of `f128`. For example, + /// they consider negative and positive zero equal, while `total_cmp` + /// doesn't. + /// + /// The interpretation of the signaling NaN bit follows the definition in + /// the IEEE 754 standard, which may not match the interpretation by some of + /// the older, non-conformant (e.g. MIPS) hardware implementations. + /// + /// # Example + /// + /// ``` + /// #![feature(f128)] + /// + /// struct GoodBoy { + /// name: &'static str, + /// weight: f128, + /// } + /// + /// let mut bois = vec![ + /// GoodBoy { name: "Pucci", weight: 0.1 }, + /// GoodBoy { name: "Woofer", weight: 99.0 }, + /// GoodBoy { name: "Yapper", weight: 10.0 }, + /// GoodBoy { name: "Chonk", weight: f128::INFINITY }, + /// GoodBoy { name: "Abs. Unit", weight: f128::NAN }, + /// GoodBoy { name: "Floaty", weight: -5.0 }, + /// ]; + /// + /// bois.sort_by(|a, b| a.weight.total_cmp(&b.weight)); + /// + /// // `f128::NAN` could be positive or negative, which will affect the sort order. + /// if f128::NAN.is_sign_negative() { + /// bois.into_iter().map(|b| b.weight) + /// .zip([f128::NAN, -5.0, 0.1, 10.0, 99.0, f128::INFINITY].iter()) + /// .for_each(|(a, b)| assert_eq!(a.to_bits(), b.to_bits())) + /// } else { + /// bois.into_iter().map(|b| b.weight) + /// .zip([-5.0, 0.1, 10.0, 99.0, f128::INFINITY, f128::NAN].iter()) + /// .for_each(|(a, b)| assert_eq!(a.to_bits(), b.to_bits())) + /// } + /// ``` + #[inline] + #[must_use] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering { + let mut left = self.to_bits() as i128; + let mut right = other.to_bits() as i128; + + // In case of negatives, flip all the bits except the sign + // to achieve a similar layout as two's complement integers + // + // Why does this work? IEEE 754 floats consist of three fields: + // Sign bit, exponent and mantissa. The set of exponent and mantissa + // fields as a whole have the property that their bitwise order is + // equal to the numeric magnitude where the magnitude is defined. + // The magnitude is not normally defined on NaN values, but + // IEEE 754 totalOrder defines the NaN values also to follow the + // bitwise order. This leads to order explained in the doc comment. + // However, the representation of magnitude is the same for negative + // and positive numbers – only the sign bit is different. + // To easily compare the floats as signed integers, we need to + // flip the exponent and mantissa bits in case of negative numbers. + // We effectively convert the numbers to "two's complement" form. + // + // To do the flipping, we construct a mask and XOR against it. + // We branchlessly calculate an "all-ones except for the sign bit" + // mask from negative-signed values: right shifting sign-extends + // the integer, so we "fill" the mask with sign bits, and then + // convert to unsigned to push one more zero bit. + // On positive values, the mask is all zeros, so it's a no-op. + left ^= (((left >> 127) as u128) >> 1) as i128; + right ^= (((right >> 127) as u128) >> 1) as i128; + + left.cmp(&right) + } + + /// Restrict a value to a certain interval unless it is NaN. + /// + /// Returns `max` if `self` is greater than `max`, and `min` if `self` is + /// less than `min`. Otherwise this returns `self`. + /// + /// Note that this function returns NaN if the initial value was NaN as + /// well. + /// + /// # Panics + /// + /// Panics if `min > max`, `min` is NaN, or `max` is NaN. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # // FIXME(f16_f128): remove when `{eq,gt,unord}tf` are available + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// assert!((-3.0f128).clamp(-2.0, 1.0) == -2.0); + /// assert!((0.0f128).clamp(-2.0, 1.0) == 0.0); + /// assert!((2.0f128).clamp(-2.0, 1.0) == 1.0); + /// assert!((f128::NAN).clamp(-2.0, 1.0).is_nan()); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub fn clamp(mut self, min: f128, max: f128) -> f128 { + assert!(min <= max, "min > max, or either was NaN. min = {min:?}, max = {max:?}"); + if self < min { + self = min; + } + if self > max { + self = max; + } + self + } } diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 4b0c25fcce967..3c58b0af9c27e 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -11,6 +11,7 @@ #![unstable(feature = "f16", issue = "116909")] +use crate::convert::FloatToInt; use crate::mem; /// Basic mathematical constants. @@ -215,11 +216,49 @@ impl f16 { #[unstable(feature = "f16", issue = "116909")] pub const MAX_10_EXP: i32 = 4; + /// Not a Number (NaN). + /// + /// Note that IEEE 754 doesn't define just a single NaN value; + /// a plethora of bit patterns are considered to be NaN. + /// Furthermore, the standard makes a difference + /// between a "signaling" and a "quiet" NaN, + /// and allows inspecting its "payload" (the unspecified bits in the bit pattern). + /// This constant isn't guaranteed to equal to any specific NaN bitpattern, + /// and the stability of its representation over Rust versions + /// and target platforms isn't guaranteed. + #[cfg(not(bootstrap))] + #[allow(clippy::eq_op)] + #[rustc_diagnostic_item = "f16_nan"] + #[unstable(feature = "f16", issue = "116909")] + pub const NAN: f16 = 0.0_f16 / 0.0_f16; + + /// Infinity (∞). + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + pub const INFINITY: f16 = 1.0_f16 / 0.0_f16; + + /// Negative infinity (−∞). + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + pub const NEG_INFINITY: f16 = -1.0_f16 / 0.0_f16; + + /// Sign bit + #[cfg(not(bootstrap))] + const SIGN_MASK: u16 = 0x8000; + + /// Minimum representable positive value (min subnormal) + #[cfg(not(bootstrap))] + const TINY_BITS: u16 = 0x1; + + /// Minimum representable negative value (min negative subnormal) + #[cfg(not(bootstrap))] + const NEG_TINY_BITS: u16 = Self::TINY_BITS | Self::SIGN_MASK; + /// Returns `true` if this value is NaN. /// /// ``` /// #![feature(f16)] - /// # #[cfg(target_arch = "x86_64")] { // FIXME(f16_f128): remove when ABI bugs are fixed + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 /// /// let nan = f16::NAN; /// let f = 7.0_f16; @@ -237,6 +276,74 @@ impl f16 { self != self } + // FIXMxE(#50145): `abs` is publicly unavailable in core due to + // concerns about portability, so this implementation is for + // private use internally. + #[inline] + #[cfg(not(bootstrap))] + #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] + pub(crate) const fn abs_private(self) -> f16 { + // SAFETY: This transmutation is fine. Probably. For the reasons std is using it. + unsafe { mem::transmute::(mem::transmute::(self) & !Self::SIGN_MASK) } + } + + /// Returns `true` if this value is positive infinity or negative infinity, and + /// `false` otherwise. + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let f = 7.0f16; + /// let inf = f16::INFINITY; + /// let neg_inf = f16::NEG_INFINITY; + /// let nan = f16::NAN; + /// + /// assert!(!f.is_infinite()); + /// assert!(!nan.is_infinite()); + /// + /// assert!(inf.is_infinite()); + /// assert!(neg_inf.is_infinite()); + /// # } + /// ``` + #[inline] + #[must_use] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] + pub const fn is_infinite(self) -> bool { + (self == f16::INFINITY) | (self == f16::NEG_INFINITY) + } + + /// Returns `true` if this number is neither infinite nor NaN. + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let f = 7.0f16; + /// let inf: f16 = f16::INFINITY; + /// let neg_inf: f16 = f16::NEG_INFINITY; + /// let nan: f16 = f16::NAN; + /// + /// assert!(f.is_finite()); + /// + /// assert!(!nan.is_finite()); + /// assert!(!inf.is_finite()); + /// assert!(!neg_inf.is_finite()); + /// # } + /// ``` + #[inline] + #[must_use] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")] + pub const fn is_finite(self) -> bool { + // There's no need to handle NaN separately: if self is NaN, + // the comparison is not true, exactly as desired. + self.abs_private() < Self::INFINITY + } + /// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with /// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that @@ -286,6 +393,220 @@ impl f16 { (self.to_bits() & (1 << 15)) != 0 } + /// Returns the least number greater than `self`. + /// + /// Let `TINY` be the smallest representable positive `f16`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`NEG_INFINITY`], this returns [`MIN`]; + /// - if `self` is `-TINY`, this returns -0.0; + /// - if `self` is -0.0 or +0.0, this returns `TINY`; + /// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; + /// - otherwise the unique least value greater than `self` is returned. + /// + /// The identity `x.next_up() == -(-x).next_down()` holds for all non-NaN `x`. When `x` + /// is finite `x == x.next_up().next_down()` also holds. + /// + /// ```rust + /// #![feature(f16)] + /// #![feature(float_next_up_down)] + /// # // FIXME(f16_f128): ABI issues on MSVC + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// // f16::EPSILON is the difference between 1.0 and the next number up. + /// assert_eq!(1.0f16.next_up(), 1.0 + f16::EPSILON); + /// // But not for most numbers. + /// assert!(0.1f16.next_up() < 0.1 + f16::EPSILON); + /// assert_eq!(4356f16.next_up(), 4360.0); + /// # } + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + // #[unstable(feature = "float_next_up_down", issue = "91399")] + pub fn next_up(self) -> Self { + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. + let bits = self.to_bits(); + if self.is_nan() || bits == Self::INFINITY.to_bits() { + return self; + } + + let abs = bits & !Self::SIGN_MASK; + let next_bits = if abs == 0 { + Self::TINY_BITS + } else if bits == abs { + bits + 1 + } else { + bits - 1 + }; + Self::from_bits(next_bits) + } + + /// Returns the greatest number less than `self`. + /// + /// Let `TINY` be the smallest representable positive `f16`. Then, + /// - if `self.is_nan()`, this returns `self`; + /// - if `self` is [`INFINITY`], this returns [`MAX`]; + /// - if `self` is `TINY`, this returns 0.0; + /// - if `self` is -0.0 or +0.0, this returns `-TINY`; + /// - if `self` is [`MIN`] or [`NEG_INFINITY`], this returns [`NEG_INFINITY`]; + /// - otherwise the unique greatest value less than `self` is returned. + /// + /// The identity `x.next_down() == -(-x).next_up()` holds for all non-NaN `x`. When `x` + /// is finite `x == x.next_down().next_up()` also holds. + /// + /// ```rust + /// #![feature(f16)] + /// #![feature(float_next_up_down)] + /// # // FIXME(f16_f128): ABI issues on MSVC + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// + /// let x = 1.0f16; + /// // Clamp value into range [0, 1). + /// let clamped = x.clamp(0.0, 1.0f16.next_down()); + /// assert!(clamped < 1.0); + /// assert_eq!(clamped.next_up(), 1.0); + /// # } + /// ``` + /// + /// [`NEG_INFINITY`]: Self::NEG_INFINITY + /// [`INFINITY`]: Self::INFINITY + /// [`MIN`]: Self::MIN + /// [`MAX`]: Self::MAX + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + // #[unstable(feature = "float_next_up_down", issue = "91399")] + pub fn next_down(self) -> Self { + // Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing + // denormals to zero. This is in general unsound and unsupported, but here + // we do our best to still produce the correct result on such targets. + let bits = self.to_bits(); + if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { + return self; + } + + let abs = bits & !Self::SIGN_MASK; + let next_bits = if abs == 0 { + Self::NEG_TINY_BITS + } else if bits == abs { + bits - 1 + } else { + bits + 1 + }; + Self::from_bits(next_bits) + } + + /// Takes the reciprocal (inverse) of a number, `1/x`. + /// + /// ``` + /// #![feature(f16)] + /// # // FIXME(f16_f128): remove when `extendhfsf2` and `truncsfhf2` are available + /// # #[cfg(target_os = "linux")] { + /// + /// let x = 2.0_f16; + /// let abs_difference = (x.recip() - (1.0 / x)).abs(); + /// + /// assert!(abs_difference <= f16::EPSILON); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn recip(self) -> Self { + 1.0 / self + } + + /// Converts radians to degrees. + /// + /// ``` + /// #![feature(f16)] + /// # // FIXME(f16_f128): remove when `extendhfsf2` and `truncsfhf2` are available + /// # #[cfg(target_os = "linux")] { + /// + /// let angle = std::f16::consts::PI; + /// + /// let abs_difference = (angle.to_degrees() - 180.0).abs(); + /// assert!(abs_difference <= 0.5); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_degrees(self) -> Self { + // Use a literal for better precision. + const PIS_IN_180: f16 = 57.2957795130823208767981548141051703_f16; + self * PIS_IN_180 + } + + /// Converts degrees to radians. + /// + /// ``` + /// #![feature(f16)] + /// # // FIXME(f16_f128): remove when `extendhfsf2` and `truncsfhf2` are available + /// # #[cfg(target_os = "linux")] { + /// + /// let angle = 180.0f16; + /// + /// let abs_difference = (angle.to_radians() - std::f16::consts::PI).abs(); + /// + /// assert!(abs_difference <= 0.01); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_radians(self) -> f16 { + // Use a literal for better precision. + const RADS_PER_DEG: f16 = 0.017453292519943295769236907684886_f16; + self * RADS_PER_DEG + } + + /// Rounds toward zero and converts to any primitive integer type, + /// assuming that the value is finite and fits in that type. + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let value = 4.6_f16; + /// let rounded = unsafe { value.to_int_unchecked::() }; + /// assert_eq!(rounded, 4); + /// + /// let value = -128.9_f16; + /// let rounded = unsafe { value.to_int_unchecked::() }; + /// assert_eq!(rounded, i8::MIN); + /// # } + /// ``` + /// + /// # Safety + /// + /// The value must: + /// + /// * Not be `NaN` + /// * Not be infinite + /// * Be representable in the return type `Int`, after truncating off its fractional part + #[inline] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub unsafe fn to_int_unchecked(self) -> Int + where + Self: FloatToInt, + { + // SAFETY: the caller must uphold the safety contract for + // `FloatToInt::to_int_unchecked`. + unsafe { FloatToInt::::to_int_unchecked(self) } + } + /// Raw transmutation to `u16`. /// /// This is currently identical to `transmute::(self)` on all platforms. @@ -362,4 +683,293 @@ impl f16 { // Stability concerns. unsafe { mem::transmute(v) } } + + /// Return the memory representation of this floating point number as a byte array in + /// big-endian (network) byte order. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// + /// let bytes = 12.5f16.to_be_bytes(); + /// assert_eq!(bytes, [0x4a, 0x40]); + /// ``` + #[inline] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_be_bytes(self) -> [u8; 2] { + self.to_bits().to_be_bytes() + } + + /// Return the memory representation of this floating point number as a byte array in + /// little-endian byte order. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// + /// let bytes = 12.5f16.to_le_bytes(); + /// assert_eq!(bytes, [0x40, 0x4a]); + /// ``` + #[inline] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_le_bytes(self) -> [u8; 2] { + self.to_bits().to_le_bytes() + } + + /// Return the memory representation of this floating point number as a byte array in + /// native byte order. + /// + /// As the target platform's native endianness is used, portable code + /// should use [`to_be_bytes`] or [`to_le_bytes`], as appropriate, instead. + /// + /// [`to_be_bytes`]: f16::to_be_bytes + /// [`to_le_bytes`]: f16::to_le_bytes + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// + /// let bytes = 12.5f16.to_ne_bytes(); + /// assert_eq!( + /// bytes, + /// if cfg!(target_endian = "big") { + /// [0x4a, 0x40] + /// } else { + /// [0x40, 0x4a] + /// } + /// ); + /// ``` + #[inline] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn to_ne_bytes(self) -> [u8; 2] { + self.to_bits().to_ne_bytes() + } + + /// Create a floating point value from its representation as a byte array in big endian. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let value = f16::from_be_bytes([0x4a, 0x40]); + /// assert_eq!(value, 12.5); + /// # } + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "f16", issue = "116909")] + pub fn from_be_bytes(bytes: [u8; 2]) -> Self { + Self::from_bits(u16::from_be_bytes(bytes)) + } + + /// Create a floating point value from its representation as a byte array in little endian. + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let value = f16::from_le_bytes([0x40, 0x4a]); + /// assert_eq!(value, 12.5); + /// # } + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "f16", issue = "116909")] + pub fn from_le_bytes(bytes: [u8; 2]) -> Self { + Self::from_bits(u16::from_le_bytes(bytes)) + } + + /// Create a floating point value from its representation as a byte array in native endian. + /// + /// As the target platform's native endianness is used, portable code + /// likely wants to use [`from_be_bytes`] or [`from_le_bytes`], as + /// appropriate instead. + /// + /// [`from_be_bytes`]: f16::from_be_bytes + /// [`from_le_bytes`]: f16::from_le_bytes + /// + /// See [`from_bits`](Self::from_bits) for some discussion of the + /// portability of this operation (there are almost no issues). + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// let value = f16::from_ne_bytes(if cfg!(target_endian = "big") { + /// [0x4a, 0x40] + /// } else { + /// [0x40, 0x4a] + /// }); + /// assert_eq!(value, 12.5); + /// # } + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "f16", issue = "116909")] + pub fn from_ne_bytes(bytes: [u8; 2]) -> Self { + Self::from_bits(u16::from_ne_bytes(bytes)) + } + + /// Return the ordering between `self` and `other`. + /// + /// Unlike the standard partial comparison between floating point numbers, + /// this comparison always produces an ordering in accordance to + /// the `totalOrder` predicate as defined in the IEEE 754 (2008 revision) + /// floating point standard. The values are ordered in the following sequence: + /// + /// - negative quiet NaN + /// - negative signaling NaN + /// - negative infinity + /// - negative numbers + /// - negative subnormal numbers + /// - negative zero + /// - positive zero + /// - positive subnormal numbers + /// - positive numbers + /// - positive infinity + /// - positive signaling NaN + /// - positive quiet NaN. + /// + /// The ordering established by this function does not always agree with the + /// [`PartialOrd`] and [`PartialEq`] implementations of `f16`. For example, + /// they consider negative and positive zero equal, while `total_cmp` + /// doesn't. + /// + /// The interpretation of the signaling NaN bit follows the definition in + /// the IEEE 754 standard, which may not match the interpretation by some of + /// the older, non-conformant (e.g. MIPS) hardware implementations. + /// + /// # Example + /// + /// ``` + /// #![feature(f16)] + /// + /// struct GoodBoy { + /// name: &'static str, + /// weight: f16, + /// } + /// + /// let mut bois = vec![ + /// GoodBoy { name: "Pucci", weight: 0.1 }, + /// GoodBoy { name: "Woofer", weight: 99.0 }, + /// GoodBoy { name: "Yapper", weight: 10.0 }, + /// GoodBoy { name: "Chonk", weight: f16::INFINITY }, + /// GoodBoy { name: "Abs. Unit", weight: f16::NAN }, + /// GoodBoy { name: "Floaty", weight: -5.0 }, + /// ]; + /// + /// bois.sort_by(|a, b| a.weight.total_cmp(&b.weight)); + /// + /// // `f16::NAN` could be positive or negative, which will affect the sort order. + /// if f16::NAN.is_sign_negative() { + /// bois.into_iter().map(|b| b.weight) + /// .zip([f16::NAN, -5.0, 0.1, 10.0, 99.0, f16::INFINITY].iter()) + /// .for_each(|(a, b)| assert_eq!(a.to_bits(), b.to_bits())) + /// } else { + /// bois.into_iter().map(|b| b.weight) + /// .zip([-5.0, 0.1, 10.0, 99.0, f16::INFINITY, f16::NAN].iter()) + /// .for_each(|(a, b)| assert_eq!(a.to_bits(), b.to_bits())) + /// } + /// ``` + #[inline] + #[must_use] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + pub fn total_cmp(&self, other: &Self) -> crate::cmp::Ordering { + let mut left = self.to_bits() as i16; + let mut right = other.to_bits() as i16; + + // In case of negatives, flip all the bits except the sign + // to achieve a similar layout as two's complement integers + // + // Why does this work? IEEE 754 floats consist of three fields: + // Sign bit, exponent and mantissa. The set of exponent and mantissa + // fields as a whole have the property that their bitwise order is + // equal to the numeric magnitude where the magnitude is defined. + // The magnitude is not normally defined on NaN values, but + // IEEE 754 totalOrder defines the NaN values also to follow the + // bitwise order. This leads to order explained in the doc comment. + // However, the representation of magnitude is the same for negative + // and positive numbers – only the sign bit is different. + // To easily compare the floats as signed integers, we need to + // flip the exponent and mantissa bits in case of negative numbers. + // We effectively convert the numbers to "two's complement" form. + // + // To do the flipping, we construct a mask and XOR against it. + // We branchlessly calculate an "all-ones except for the sign bit" + // mask from negative-signed values: right shifting sign-extends + // the integer, so we "fill" the mask with sign bits, and then + // convert to unsigned to push one more zero bit. + // On positive values, the mask is all zeros, so it's a no-op. + left ^= (((left >> 15) as u16) >> 1) as i16; + right ^= (((right >> 15) as u16) >> 1) as i16; + + left.cmp(&right) + } + + /// Restrict a value to a certain interval unless it is NaN. + /// + /// Returns `max` if `self` is greater than `max`, and `min` if `self` is + /// less than `min`. Otherwise this returns `self`. + /// + /// Note that this function returns NaN if the initial value was NaN as + /// well. + /// + /// # Panics + /// + /// Panics if `min > max`, `min` is NaN, or `max` is NaN. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885 + /// + /// assert!((-3.0f16).clamp(-2.0, 1.0) == -2.0); + /// assert!((0.0f16).clamp(-2.0, 1.0) == 0.0); + /// assert!((2.0f16).clamp(-2.0, 1.0) == 1.0); + /// assert!((f16::NAN).clamp(-2.0, 1.0).is_nan()); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub fn clamp(mut self, min: f16, max: f16) -> f16 { + assert!(min <= max, "min > max, or either was NaN. min = {min:?}, max = {max:?}"); + if self < min { + self = min; + } + if self > max { + self = max; + } + self + } } diff --git a/library/std/src/f128.rs b/library/std/src/f128.rs index 491235a872eaf..0591c6f517b44 100644 --- a/library/std/src/f128.rs +++ b/library/std/src/f128.rs @@ -32,4 +32,34 @@ impl f128 { pub fn powi(self, n: i32) -> f128 { unsafe { intrinsics::powif128(self, n) } } + + /// Computes the absolute value of `self`. + /// + /// This function always returns the precise result. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// # #[cfg(reliable_f128)] { // FIXME(f16_f128): reliable_f128 + /// + /// let x = 3.5_f128; + /// let y = -3.5_f128; + /// + /// assert_eq!(x.abs(), x); + /// assert_eq!(y.abs(), -y); + /// + /// assert!(f128::NAN.abs().is_nan()); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f128", issue = "116909")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub fn abs(self) -> Self { + // FIXME(f16_f128): replace with `intrinsics::fabsf128` when available + // We don't do this now because LLVM has lowering bugs for f128 math. + Self::from_bits(self.to_bits() & !(1 << 127)) + } } diff --git a/library/std/src/f16.rs b/library/std/src/f16.rs index 1cb655ffabd84..d48518622999a 100644 --- a/library/std/src/f16.rs +++ b/library/std/src/f16.rs @@ -32,4 +32,33 @@ impl f16 { pub fn powi(self, n: i32) -> f16 { unsafe { intrinsics::powif16(self, n) } } + + /// Computes the absolute value of `self`. + /// + /// This function always returns the precise result. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// # #[cfg(reliable_f16)] { + /// + /// let x = 3.5_f16; + /// let y = -3.5_f16; + /// + /// assert_eq!(x.abs(), x); + /// assert_eq!(y.abs(), -y); + /// + /// assert!(f16::NAN.abs().is_nan()); + /// # } + /// ``` + #[inline] + #[cfg(not(bootstrap))] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "f16", issue = "116909")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub fn abs(self) -> Self { + // FIXME(f16_f128): replace with `intrinsics::fabsf16` when available + Self::from_bits(self.to_bits() & !(1 << 15)) + } } From e181297c8cc814b2d3873b3a3b9d9d0182c660f1 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 18 Jun 2024 18:27:28 -0500 Subject: [PATCH 6/6] Add tests for `f16` and `f128` This suite tests all library functions that are now available for the types. Tests are only run on certain platforms where `f16` and `f128` are known to work (have symbols available and don't crash LLVM). --- library/std/src/f128/tests.rs | 527 ++++++++++++++++++++++++++++++++-- library/std/src/f16/tests.rs | 526 +++++++++++++++++++++++++++++++-- library/std/src/macros.rs | 11 +- 3 files changed, 1029 insertions(+), 35 deletions(-) diff --git a/library/std/src/f128/tests.rs b/library/std/src/f128/tests.rs index b64c7f856a15f..bd7a921c502a7 100644 --- a/library/std/src/f128/tests.rs +++ b/library/std/src/f128/tests.rs @@ -1,29 +1,31 @@ -#![allow(dead_code)] // FIXME(f16_f128): remove once constants are used +#![cfg(not(bootstrap))] +// FIXME(f16_f128): only tested on platforms that have symbols and aren't buggy +#![cfg(reliable_f128)] + +use crate::f128::consts; +use crate::num::*; /// Smallest number const TINY_BITS: u128 = 0x1; + /// Next smallest number const TINY_UP_BITS: u128 = 0x2; + /// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 -const MAX_DOWN_BITS: u128 = 0x7ffeffffffffffffffffffffffffffff; +const MAX_DOWN_BITS: u128 = 0x7ffefffffffffffffffffffffffffffe; + /// Zeroed exponent, full significant const LARGEST_SUBNORMAL_BITS: u128 = 0x0000ffffffffffffffffffffffffffff; + /// Exponent = 0b1, zeroed significand const SMALLEST_NORMAL_BITS: u128 = 0x00010000000000000000000000000000; + /// First pattern over the mantissa const NAN_MASK1: u128 = 0x0000aaaaaaaaaaaaaaaaaaaaaaaaaaaa; + /// Second pattern over the mantissa const NAN_MASK2: u128 = 0x00005555555555555555555555555555; -/// Compare by value -#[allow(unused_macros)] -macro_rules! assert_f128_eq { - ($a:expr, $b:expr) => { - let (l, r): (&f128, &f128) = (&$a, &$b); - assert_eq!(*l, *r, "\na: {:#0130x}\nb: {:#0130x}", l.to_bits(), r.to_bits()) - }; -} - /// Compare by representation #[allow(unused_macros)] macro_rules! assert_f128_biteq { @@ -31,10 +33,503 @@ macro_rules! assert_f128_biteq { let (l, r): (&f128, &f128) = (&$a, &$b); let lb = l.to_bits(); let rb = r.to_bits(); - assert_eq!( - lb, rb, - "float {:?} is not bitequal to {:?}.\na: {:#0130x}\nb: {:#0130x}", - *l, *r, lb, rb - ); + assert_eq!(lb, rb, "float {l:?} is not bitequal to {r:?}.\na: {lb:#034x}\nb: {rb:#034x}"); }; } + +#[test] +fn test_num_f128() { + test_num(10f128, 2f128); +} + +// FIXME(f16_f128): add min and max tests when available + +#[test] +fn test_nan() { + let nan: f128 = f128::NAN; + assert!(nan.is_nan()); + assert!(!nan.is_infinite()); + assert!(!nan.is_finite()); + assert!(nan.is_sign_positive()); + assert!(!nan.is_sign_negative()); + // FIXME(f16_f128): classify + // assert!(!nan.is_normal()); + // assert_eq!(Fp::Nan, nan.classify()); +} + +#[test] +fn test_infinity() { + let inf: f128 = f128::INFINITY; + assert!(inf.is_infinite()); + assert!(!inf.is_finite()); + assert!(inf.is_sign_positive()); + assert!(!inf.is_sign_negative()); + assert!(!inf.is_nan()); + // FIXME(f16_f128): classify + // assert!(!inf.is_normal()); + // assert_eq!(Fp::Infinite, inf.classify()); +} + +#[test] +fn test_neg_infinity() { + let neg_inf: f128 = f128::NEG_INFINITY; + assert!(neg_inf.is_infinite()); + assert!(!neg_inf.is_finite()); + assert!(!neg_inf.is_sign_positive()); + assert!(neg_inf.is_sign_negative()); + assert!(!neg_inf.is_nan()); + // FIXME(f16_f128): classify + // assert!(!neg_inf.is_normal()); + // assert_eq!(Fp::Infinite, neg_inf.classify()); +} + +#[test] +fn test_zero() { + let zero: f128 = 0.0f128; + assert_eq!(0.0, zero); + assert!(!zero.is_infinite()); + assert!(zero.is_finite()); + assert!(zero.is_sign_positive()); + assert!(!zero.is_sign_negative()); + assert!(!zero.is_nan()); + // FIXME(f16_f128): classify + // assert!(!zero.is_normal()); + // assert_eq!(Fp::Zero, zero.classify()); +} + +#[test] +fn test_neg_zero() { + let neg_zero: f128 = -0.0; + assert_eq!(0.0, neg_zero); + assert!(!neg_zero.is_infinite()); + assert!(neg_zero.is_finite()); + assert!(!neg_zero.is_sign_positive()); + assert!(neg_zero.is_sign_negative()); + assert!(!neg_zero.is_nan()); + // FIXME(f16_f128): classify + // assert!(!neg_zero.is_normal()); + // assert_eq!(Fp::Zero, neg_zero.classify()); +} + +#[test] +fn test_one() { + let one: f128 = 1.0f128; + assert_eq!(1.0, one); + assert!(!one.is_infinite()); + assert!(one.is_finite()); + assert!(one.is_sign_positive()); + assert!(!one.is_sign_negative()); + assert!(!one.is_nan()); + // FIXME(f16_f128): classify + // assert!(one.is_normal()); + // assert_eq!(Fp::Normal, one.classify()); +} + +#[test] +fn test_is_nan() { + let nan: f128 = f128::NAN; + let inf: f128 = f128::INFINITY; + let neg_inf: f128 = f128::NEG_INFINITY; + assert!(nan.is_nan()); + assert!(!0.0f128.is_nan()); + assert!(!5.3f128.is_nan()); + assert!(!(-10.732f128).is_nan()); + assert!(!inf.is_nan()); + assert!(!neg_inf.is_nan()); +} + +#[test] +fn test_is_infinite() { + let nan: f128 = f128::NAN; + let inf: f128 = f128::INFINITY; + let neg_inf: f128 = f128::NEG_INFINITY; + assert!(!nan.is_infinite()); + assert!(inf.is_infinite()); + assert!(neg_inf.is_infinite()); + assert!(!0.0f128.is_infinite()); + assert!(!42.8f128.is_infinite()); + assert!(!(-109.2f128).is_infinite()); +} + +#[test] +fn test_is_finite() { + let nan: f128 = f128::NAN; + let inf: f128 = f128::INFINITY; + let neg_inf: f128 = f128::NEG_INFINITY; + assert!(!nan.is_finite()); + assert!(!inf.is_finite()); + assert!(!neg_inf.is_finite()); + assert!(0.0f128.is_finite()); + assert!(42.8f128.is_finite()); + assert!((-109.2f128).is_finite()); +} + +// FIXME(f16_f128): add `test_is_normal` and `test_classify` when classify is working +// FIXME(f16_f128): add missing math functions when available + +#[test] +fn test_abs() { + assert_eq!(f128::INFINITY.abs(), f128::INFINITY); + assert_eq!(1f128.abs(), 1f128); + assert_eq!(0f128.abs(), 0f128); + assert_eq!((-0f128).abs(), 0f128); + assert_eq!((-1f128).abs(), 1f128); + assert_eq!(f128::NEG_INFINITY.abs(), f128::INFINITY); + assert_eq!((1f128 / f128::NEG_INFINITY).abs(), 0f128); + assert!(f128::NAN.abs().is_nan()); +} + +#[test] +fn test_is_sign_positive() { + assert!(f128::INFINITY.is_sign_positive()); + assert!(1f128.is_sign_positive()); + assert!(0f128.is_sign_positive()); + assert!(!(-0f128).is_sign_positive()); + assert!(!(-1f128).is_sign_positive()); + assert!(!f128::NEG_INFINITY.is_sign_positive()); + assert!(!(1f128 / f128::NEG_INFINITY).is_sign_positive()); + assert!(f128::NAN.is_sign_positive()); + assert!(!(-f128::NAN).is_sign_positive()); +} + +#[test] +fn test_is_sign_negative() { + assert!(!f128::INFINITY.is_sign_negative()); + assert!(!1f128.is_sign_negative()); + assert!(!0f128.is_sign_negative()); + assert!((-0f128).is_sign_negative()); + assert!((-1f128).is_sign_negative()); + assert!(f128::NEG_INFINITY.is_sign_negative()); + assert!((1f128 / f128::NEG_INFINITY).is_sign_negative()); + assert!(!f128::NAN.is_sign_negative()); + assert!((-f128::NAN).is_sign_negative()); +} + +#[test] +fn test_next_up() { + let tiny = f128::from_bits(TINY_BITS); + let tiny_up = f128::from_bits(TINY_UP_BITS); + let max_down = f128::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f128::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f128::from_bits(SMALLEST_NORMAL_BITS); + assert_f128_biteq!(f128::NEG_INFINITY.next_up(), f128::MIN); + assert_f128_biteq!(f128::MIN.next_up(), -max_down); + assert_f128_biteq!((-1.0 - f128::EPSILON).next_up(), -1.0); + assert_f128_biteq!((-smallest_normal).next_up(), -largest_subnormal); + assert_f128_biteq!((-tiny_up).next_up(), -tiny); + assert_f128_biteq!((-tiny).next_up(), -0.0f128); + assert_f128_biteq!((-0.0f128).next_up(), tiny); + assert_f128_biteq!(0.0f128.next_up(), tiny); + assert_f128_biteq!(tiny.next_up(), tiny_up); + assert_f128_biteq!(largest_subnormal.next_up(), smallest_normal); + assert_f128_biteq!(1.0f128.next_up(), 1.0 + f128::EPSILON); + assert_f128_biteq!(f128::MAX.next_up(), f128::INFINITY); + assert_f128_biteq!(f128::INFINITY.next_up(), f128::INFINITY); + + // Check that NaNs roundtrip. + let nan0 = f128::NAN; + let nan1 = f128::from_bits(f128::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f128::from_bits(f128::NAN.to_bits() ^ 0x0055_5555); + assert_f128_biteq!(nan0.next_up(), nan0); + assert_f128_biteq!(nan1.next_up(), nan1); + assert_f128_biteq!(nan2.next_up(), nan2); +} + +#[test] +fn test_next_down() { + let tiny = f128::from_bits(TINY_BITS); + let tiny_up = f128::from_bits(TINY_UP_BITS); + let max_down = f128::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f128::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f128::from_bits(SMALLEST_NORMAL_BITS); + assert_f128_biteq!(f128::NEG_INFINITY.next_down(), f128::NEG_INFINITY); + assert_f128_biteq!(f128::MIN.next_down(), f128::NEG_INFINITY); + assert_f128_biteq!((-max_down).next_down(), f128::MIN); + assert_f128_biteq!((-1.0f128).next_down(), -1.0 - f128::EPSILON); + assert_f128_biteq!((-largest_subnormal).next_down(), -smallest_normal); + assert_f128_biteq!((-tiny).next_down(), -tiny_up); + assert_f128_biteq!((-0.0f128).next_down(), -tiny); + assert_f128_biteq!((0.0f128).next_down(), -tiny); + assert_f128_biteq!(tiny.next_down(), 0.0f128); + assert_f128_biteq!(tiny_up.next_down(), tiny); + assert_f128_biteq!(smallest_normal.next_down(), largest_subnormal); + assert_f128_biteq!((1.0 + f128::EPSILON).next_down(), 1.0f128); + assert_f128_biteq!(f128::MAX.next_down(), max_down); + assert_f128_biteq!(f128::INFINITY.next_down(), f128::MAX); + + // Check that NaNs roundtrip. + let nan0 = f128::NAN; + let nan1 = f128::from_bits(f128::NAN.to_bits() ^ 0x002a_aaaa); + let nan2 = f128::from_bits(f128::NAN.to_bits() ^ 0x0055_5555); + assert_f128_biteq!(nan0.next_down(), nan0); + assert_f128_biteq!(nan1.next_down(), nan1); + assert_f128_biteq!(nan2.next_down(), nan2); +} + +#[test] +fn test_recip() { + let nan: f128 = f128::NAN; + let inf: f128 = f128::INFINITY; + let neg_inf: f128 = f128::NEG_INFINITY; + assert_eq!(1.0f128.recip(), 1.0); + assert_eq!(2.0f128.recip(), 0.5); + assert_eq!((-0.4f128).recip(), -2.5); + assert_eq!(0.0f128.recip(), inf); + assert!(nan.recip().is_nan()); + assert_eq!(inf.recip(), 0.0); + assert_eq!(neg_inf.recip(), 0.0); +} + +#[test] +fn test_to_degrees() { + let pi: f128 = consts::PI; + let nan: f128 = f128::NAN; + let inf: f128 = f128::INFINITY; + let neg_inf: f128 = f128::NEG_INFINITY; + assert_eq!(0.0f128.to_degrees(), 0.0); + assert_approx_eq!((-5.8f128).to_degrees(), -332.315521); + assert_eq!(pi.to_degrees(), 180.0); + assert!(nan.to_degrees().is_nan()); + assert_eq!(inf.to_degrees(), inf); + assert_eq!(neg_inf.to_degrees(), neg_inf); + assert_eq!(1_f128.to_degrees(), 57.2957795130823208767981548141051703); +} + +#[test] +fn test_to_radians() { + let pi: f128 = consts::PI; + let nan: f128 = f128::NAN; + let inf: f128 = f128::INFINITY; + let neg_inf: f128 = f128::NEG_INFINITY; + assert_eq!(0.0f128.to_radians(), 0.0); + assert_approx_eq!(154.6f128.to_radians(), 2.698279); + assert_approx_eq!((-332.31f128).to_radians(), -5.799903); + // check approx rather than exact because round trip for pi doesn't fall on an exactly + // representable value (unlike `f32` and `f64`). + assert_approx_eq!(180.0f128.to_radians(), pi); + assert!(nan.to_radians().is_nan()); + assert_eq!(inf.to_radians(), inf); + assert_eq!(neg_inf.to_radians(), neg_inf); +} + +#[test] +fn test_real_consts() { + // FIXME(f16_f128): add math tests when available + use super::consts; + + let pi: f128 = consts::PI; + let frac_pi_2: f128 = consts::FRAC_PI_2; + let frac_pi_3: f128 = consts::FRAC_PI_3; + let frac_pi_4: f128 = consts::FRAC_PI_4; + let frac_pi_6: f128 = consts::FRAC_PI_6; + let frac_pi_8: f128 = consts::FRAC_PI_8; + let frac_1_pi: f128 = consts::FRAC_1_PI; + let frac_2_pi: f128 = consts::FRAC_2_PI; + // let frac_2_sqrtpi: f128 = consts::FRAC_2_SQRT_PI; + // let sqrt2: f128 = consts::SQRT_2; + // let frac_1_sqrt2: f128 = consts::FRAC_1_SQRT_2; + // let e: f128 = consts::E; + // let log2_e: f128 = consts::LOG2_E; + // let log10_e: f128 = consts::LOG10_E; + // let ln_2: f128 = consts::LN_2; + // let ln_10: f128 = consts::LN_10; + + assert_approx_eq!(frac_pi_2, pi / 2f128); + assert_approx_eq!(frac_pi_3, pi / 3f128); + assert_approx_eq!(frac_pi_4, pi / 4f128); + assert_approx_eq!(frac_pi_6, pi / 6f128); + assert_approx_eq!(frac_pi_8, pi / 8f128); + assert_approx_eq!(frac_1_pi, 1f128 / pi); + assert_approx_eq!(frac_2_pi, 2f128 / pi); + // assert_approx_eq!(frac_2_sqrtpi, 2f128 / pi.sqrt()); + // assert_approx_eq!(sqrt2, 2f128.sqrt()); + // assert_approx_eq!(frac_1_sqrt2, 1f128 / 2f128.sqrt()); + // assert_approx_eq!(log2_e, e.log2()); + // assert_approx_eq!(log10_e, e.log10()); + // assert_approx_eq!(ln_2, 2f128.ln()); + // assert_approx_eq!(ln_10, 10f128.ln()); +} + +#[test] +fn test_float_bits_conv() { + assert_eq!((1f128).to_bits(), 0x3fff0000000000000000000000000000); + assert_eq!((12.5f128).to_bits(), 0x40029000000000000000000000000000); + assert_eq!((1337f128).to_bits(), 0x40094e40000000000000000000000000); + assert_eq!((-14.25f128).to_bits(), 0xc002c800000000000000000000000000); + assert_approx_eq!(f128::from_bits(0x3fff0000000000000000000000000000), 1.0); + assert_approx_eq!(f128::from_bits(0x40029000000000000000000000000000), 12.5); + assert_approx_eq!(f128::from_bits(0x40094e40000000000000000000000000), 1337.0); + assert_approx_eq!(f128::from_bits(0xc002c800000000000000000000000000), -14.25); + + // Check that NaNs roundtrip their bits regardless of signaling-ness + // 0xA is 0b1010; 0x5 is 0b0101 -- so these two together clobbers all the mantissa bits + let masked_nan1 = f128::NAN.to_bits() ^ NAN_MASK1; + let masked_nan2 = f128::NAN.to_bits() ^ NAN_MASK2; + assert!(f128::from_bits(masked_nan1).is_nan()); + assert!(f128::from_bits(masked_nan2).is_nan()); + + assert_eq!(f128::from_bits(masked_nan1).to_bits(), masked_nan1); + assert_eq!(f128::from_bits(masked_nan2).to_bits(), masked_nan2); +} + +#[test] +#[should_panic] +fn test_clamp_min_greater_than_max() { + let _ = 1.0f128.clamp(3.0, 1.0); +} + +#[test] +#[should_panic] +fn test_clamp_min_is_nan() { + let _ = 1.0f128.clamp(f128::NAN, 1.0); +} + +#[test] +#[should_panic] +fn test_clamp_max_is_nan() { + let _ = 1.0f128.clamp(3.0, f128::NAN); +} + +#[test] +fn test_total_cmp() { + use core::cmp::Ordering; + + fn quiet_bit_mask() -> u128 { + 1 << (f128::MANTISSA_DIGITS - 2) + } + + // FIXME(f16_f128): test subnormals when powf is available + // fn min_subnorm() -> f128 { + // f128::MIN_POSITIVE / f128::powf(2.0, f128::MANTISSA_DIGITS as f128 - 1.0) + // } + + // fn max_subnorm() -> f128 { + // f128::MIN_POSITIVE - min_subnorm() + // } + + fn q_nan() -> f128 { + f128::from_bits(f128::NAN.to_bits() | quiet_bit_mask()) + } + + fn s_nan() -> f128 { + f128::from_bits((f128::NAN.to_bits() & !quiet_bit_mask()) + 42) + } + + assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan())); + assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan())); + assert_eq!(Ordering::Equal, (-f128::INFINITY).total_cmp(&-f128::INFINITY)); + assert_eq!(Ordering::Equal, (-f128::MAX).total_cmp(&-f128::MAX)); + assert_eq!(Ordering::Equal, (-2.5_f128).total_cmp(&-2.5)); + assert_eq!(Ordering::Equal, (-1.0_f128).total_cmp(&-1.0)); + assert_eq!(Ordering::Equal, (-1.5_f128).total_cmp(&-1.5)); + assert_eq!(Ordering::Equal, (-0.5_f128).total_cmp(&-0.5)); + assert_eq!(Ordering::Equal, (-f128::MIN_POSITIVE).total_cmp(&-f128::MIN_POSITIVE)); + // assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Equal, (-0.0_f128).total_cmp(&-0.0)); + assert_eq!(Ordering::Equal, 0.0_f128.total_cmp(&0.0)); + // assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm())); + assert_eq!(Ordering::Equal, f128::MIN_POSITIVE.total_cmp(&f128::MIN_POSITIVE)); + assert_eq!(Ordering::Equal, 0.5_f128.total_cmp(&0.5)); + assert_eq!(Ordering::Equal, 1.0_f128.total_cmp(&1.0)); + assert_eq!(Ordering::Equal, 1.5_f128.total_cmp(&1.5)); + assert_eq!(Ordering::Equal, 2.5_f128.total_cmp(&2.5)); + assert_eq!(Ordering::Equal, f128::MAX.total_cmp(&f128::MAX)); + assert_eq!(Ordering::Equal, f128::INFINITY.total_cmp(&f128::INFINITY)); + assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan())); + assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan())); + + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::INFINITY)); + assert_eq!(Ordering::Less, (-f128::INFINITY).total_cmp(&-f128::MAX)); + assert_eq!(Ordering::Less, (-f128::MAX).total_cmp(&-2.5)); + assert_eq!(Ordering::Less, (-2.5_f128).total_cmp(&-1.5)); + assert_eq!(Ordering::Less, (-1.5_f128).total_cmp(&-1.0)); + assert_eq!(Ordering::Less, (-1.0_f128).total_cmp(&-0.5)); + assert_eq!(Ordering::Less, (-0.5_f128).total_cmp(&-f128::MIN_POSITIVE)); + // assert_eq!(Ordering::Less, (-f128::MIN_POSITIVE).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm())); + // assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0)); + assert_eq!(Ordering::Less, (-0.0_f128).total_cmp(&0.0)); + // assert_eq!(Ordering::Less, 0.0_f128.total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm())); + // assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f128::MIN_POSITIVE)); + assert_eq!(Ordering::Less, f128::MIN_POSITIVE.total_cmp(&0.5)); + assert_eq!(Ordering::Less, 0.5_f128.total_cmp(&1.0)); + assert_eq!(Ordering::Less, 1.0_f128.total_cmp(&1.5)); + assert_eq!(Ordering::Less, 1.5_f128.total_cmp(&2.5)); + assert_eq!(Ordering::Less, 2.5_f128.total_cmp(&f128::MAX)); + assert_eq!(Ordering::Less, f128::MAX.total_cmp(&f128::INFINITY)); + assert_eq!(Ordering::Less, f128::INFINITY.total_cmp(&s_nan())); + assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan())); + + assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan())); + assert_eq!(Ordering::Greater, (-f128::INFINITY).total_cmp(&-s_nan())); + assert_eq!(Ordering::Greater, (-f128::MAX).total_cmp(&-f128::INFINITY)); + assert_eq!(Ordering::Greater, (-2.5_f128).total_cmp(&-f128::MAX)); + assert_eq!(Ordering::Greater, (-1.5_f128).total_cmp(&-2.5)); + assert_eq!(Ordering::Greater, (-1.0_f128).total_cmp(&-1.5)); + assert_eq!(Ordering::Greater, (-0.5_f128).total_cmp(&-1.0)); + assert_eq!(Ordering::Greater, (-f128::MIN_POSITIVE).total_cmp(&-0.5)); + // assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f128::MIN_POSITIVE)); + // assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Greater, (-0.0_f128).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Greater, 0.0_f128.total_cmp(&-0.0)); + // assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0)); + // assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Greater, f128::MIN_POSITIVE.total_cmp(&max_subnorm())); + assert_eq!(Ordering::Greater, 0.5_f128.total_cmp(&f128::MIN_POSITIVE)); + assert_eq!(Ordering::Greater, 1.0_f128.total_cmp(&0.5)); + assert_eq!(Ordering::Greater, 1.5_f128.total_cmp(&1.0)); + assert_eq!(Ordering::Greater, 2.5_f128.total_cmp(&1.5)); + assert_eq!(Ordering::Greater, f128::MAX.total_cmp(&2.5)); + assert_eq!(Ordering::Greater, f128::INFINITY.total_cmp(&f128::MAX)); + assert_eq!(Ordering::Greater, s_nan().total_cmp(&f128::INFINITY)); + assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan())); + + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f128::INFINITY)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f128::MAX)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f128::MIN_POSITIVE)); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0)); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm())); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f128::MIN_POSITIVE)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f128::MAX)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f128::INFINITY)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan())); + + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::INFINITY)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::MAX)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::MIN_POSITIVE)); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0)); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm())); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f128::MIN_POSITIVE)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f128::MAX)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f128::INFINITY)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); +} diff --git a/library/std/src/f16/tests.rs b/library/std/src/f16/tests.rs index d65c43eca4bb8..bb6a811529e17 100644 --- a/library/std/src/f16/tests.rs +++ b/library/std/src/f16/tests.rs @@ -1,35 +1,37 @@ -#![allow(dead_code)] // FIXME(f16_f128): remove once constants are used +#![cfg(not(bootstrap))] +// FIXME(f16_f128): only tested on platforms that have symbols and aren't buggy +#![cfg(reliable_f16)] + +use crate::f16::consts; +use crate::num::*; // We run out of precision pretty quickly with f16 -const F16_APPROX_L1: f16 = 0.001; +// const F16_APPROX_L1: f16 = 0.001; const F16_APPROX_L2: f16 = 0.01; -const F16_APPROX_L3: f16 = 0.1; +// const F16_APPROX_L3: f16 = 0.1; const F16_APPROX_L4: f16 = 0.5; /// Smallest number const TINY_BITS: u16 = 0x1; + /// Next smallest number const TINY_UP_BITS: u16 = 0x2; + /// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 const MAX_DOWN_BITS: u16 = 0x7bfe; + /// Zeroed exponent, full significant const LARGEST_SUBNORMAL_BITS: u16 = 0x03ff; + /// Exponent = 0b1, zeroed significand const SMALLEST_NORMAL_BITS: u16 = 0x0400; + /// First pattern over the mantissa const NAN_MASK1: u16 = 0x02aa; + /// Second pattern over the mantissa const NAN_MASK2: u16 = 0x0155; -/// Compare by value -#[allow(unused_macros)] -macro_rules! assert_f16_eq { - ($a:expr, $b:expr) => { - let (l, r): (&f16, &f16) = (&$a, &$b); - assert_eq!(*l, *r, "\na: {:#018x}\nb: {:#018x}", l.to_bits(), r.to_bits()) - }; -} - /// Compare by representation #[allow(unused_macros)] macro_rules! assert_f16_biteq { @@ -37,10 +39,500 @@ macro_rules! assert_f16_biteq { let (l, r): (&f16, &f16) = (&$a, &$b); let lb = l.to_bits(); let rb = r.to_bits(); - assert_eq!( - lb, rb, - "float {:?} is not bitequal to {:?}.\na: {:#018x}\nb: {:#018x}", - *l, *r, lb, rb - ); + assert_eq!(lb, rb, "float {l:?} ({lb:#04x}) is not bitequal to {r:?} ({rb:#04x})"); }; } + +#[test] +fn test_num_f16() { + test_num(10f16, 2f16); +} + +// FIXME(f16_f128): add min and max tests when available + +#[test] +fn test_nan() { + let nan: f16 = f16::NAN; + assert!(nan.is_nan()); + assert!(!nan.is_infinite()); + assert!(!nan.is_finite()); + assert!(nan.is_sign_positive()); + assert!(!nan.is_sign_negative()); + // FIXME(f16_f128): classify + // assert!(!nan.is_normal()); + // assert_eq!(Fp::Nan, nan.classify()); +} + +#[test] +fn test_infinity() { + let inf: f16 = f16::INFINITY; + assert!(inf.is_infinite()); + assert!(!inf.is_finite()); + assert!(inf.is_sign_positive()); + assert!(!inf.is_sign_negative()); + assert!(!inf.is_nan()); + // FIXME(f16_f128): classify + // assert!(!inf.is_normal()); + // assert_eq!(Fp::Infinite, inf.classify()); +} + +#[test] +fn test_neg_infinity() { + let neg_inf: f16 = f16::NEG_INFINITY; + assert!(neg_inf.is_infinite()); + assert!(!neg_inf.is_finite()); + assert!(!neg_inf.is_sign_positive()); + assert!(neg_inf.is_sign_negative()); + assert!(!neg_inf.is_nan()); + // FIXME(f16_f128): classify + // assert!(!neg_inf.is_normal()); + // assert_eq!(Fp::Infinite, neg_inf.classify()); +} + +#[test] +fn test_zero() { + let zero: f16 = 0.0f16; + assert_eq!(0.0, zero); + assert!(!zero.is_infinite()); + assert!(zero.is_finite()); + assert!(zero.is_sign_positive()); + assert!(!zero.is_sign_negative()); + assert!(!zero.is_nan()); + // FIXME(f16_f128): classify + // assert!(!zero.is_normal()); + // assert_eq!(Fp::Zero, zero.classify()); +} + +#[test] +fn test_neg_zero() { + let neg_zero: f16 = -0.0; + assert_eq!(0.0, neg_zero); + assert!(!neg_zero.is_infinite()); + assert!(neg_zero.is_finite()); + assert!(!neg_zero.is_sign_positive()); + assert!(neg_zero.is_sign_negative()); + assert!(!neg_zero.is_nan()); + // FIXME(f16_f128): classify + // assert!(!neg_zero.is_normal()); + // assert_eq!(Fp::Zero, neg_zero.classify()); +} + +#[test] +fn test_one() { + let one: f16 = 1.0f16; + assert_eq!(1.0, one); + assert!(!one.is_infinite()); + assert!(one.is_finite()); + assert!(one.is_sign_positive()); + assert!(!one.is_sign_negative()); + assert!(!one.is_nan()); + // FIXME(f16_f128): classify + // assert!(one.is_normal()); + // assert_eq!(Fp::Normal, one.classify()); +} + +#[test] +fn test_is_nan() { + let nan: f16 = f16::NAN; + let inf: f16 = f16::INFINITY; + let neg_inf: f16 = f16::NEG_INFINITY; + assert!(nan.is_nan()); + assert!(!0.0f16.is_nan()); + assert!(!5.3f16.is_nan()); + assert!(!(-10.732f16).is_nan()); + assert!(!inf.is_nan()); + assert!(!neg_inf.is_nan()); +} + +#[test] +fn test_is_infinite() { + let nan: f16 = f16::NAN; + let inf: f16 = f16::INFINITY; + let neg_inf: f16 = f16::NEG_INFINITY; + assert!(!nan.is_infinite()); + assert!(inf.is_infinite()); + assert!(neg_inf.is_infinite()); + assert!(!0.0f16.is_infinite()); + assert!(!42.8f16.is_infinite()); + assert!(!(-109.2f16).is_infinite()); +} + +#[test] +fn test_is_finite() { + let nan: f16 = f16::NAN; + let inf: f16 = f16::INFINITY; + let neg_inf: f16 = f16::NEG_INFINITY; + assert!(!nan.is_finite()); + assert!(!inf.is_finite()); + assert!(!neg_inf.is_finite()); + assert!(0.0f16.is_finite()); + assert!(42.8f16.is_finite()); + assert!((-109.2f16).is_finite()); +} + +// FIXME(f16_f128): add `test_is_normal` and `test_classify` when classify is working +// FIXME(f16_f128): add missing math functions when available + +#[test] +fn test_abs() { + assert_eq!(f16::INFINITY.abs(), f16::INFINITY); + assert_eq!(1f16.abs(), 1f16); + assert_eq!(0f16.abs(), 0f16); + assert_eq!((-0f16).abs(), 0f16); + assert_eq!((-1f16).abs(), 1f16); + assert_eq!(f16::NEG_INFINITY.abs(), f16::INFINITY); + assert_eq!((1f16 / f16::NEG_INFINITY).abs(), 0f16); + assert!(f16::NAN.abs().is_nan()); +} + +#[test] +fn test_is_sign_positive() { + assert!(f16::INFINITY.is_sign_positive()); + assert!(1f16.is_sign_positive()); + assert!(0f16.is_sign_positive()); + assert!(!(-0f16).is_sign_positive()); + assert!(!(-1f16).is_sign_positive()); + assert!(!f16::NEG_INFINITY.is_sign_positive()); + assert!(!(1f16 / f16::NEG_INFINITY).is_sign_positive()); + assert!(f16::NAN.is_sign_positive()); + assert!(!(-f16::NAN).is_sign_positive()); +} + +#[test] +fn test_is_sign_negative() { + assert!(!f16::INFINITY.is_sign_negative()); + assert!(!1f16.is_sign_negative()); + assert!(!0f16.is_sign_negative()); + assert!((-0f16).is_sign_negative()); + assert!((-1f16).is_sign_negative()); + assert!(f16::NEG_INFINITY.is_sign_negative()); + assert!((1f16 / f16::NEG_INFINITY).is_sign_negative()); + assert!(!f16::NAN.is_sign_negative()); + assert!((-f16::NAN).is_sign_negative()); +} + +#[test] +fn test_next_up() { + let tiny = f16::from_bits(TINY_BITS); + let tiny_up = f16::from_bits(TINY_UP_BITS); + let max_down = f16::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f16::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f16::from_bits(SMALLEST_NORMAL_BITS); + assert_f16_biteq!(f16::NEG_INFINITY.next_up(), f16::MIN); + assert_f16_biteq!(f16::MIN.next_up(), -max_down); + assert_f16_biteq!((-1.0 - f16::EPSILON).next_up(), -1.0); + assert_f16_biteq!((-smallest_normal).next_up(), -largest_subnormal); + assert_f16_biteq!((-tiny_up).next_up(), -tiny); + assert_f16_biteq!((-tiny).next_up(), -0.0f16); + assert_f16_biteq!((-0.0f16).next_up(), tiny); + assert_f16_biteq!(0.0f16.next_up(), tiny); + assert_f16_biteq!(tiny.next_up(), tiny_up); + assert_f16_biteq!(largest_subnormal.next_up(), smallest_normal); + assert_f16_biteq!(1.0f16.next_up(), 1.0 + f16::EPSILON); + assert_f16_biteq!(f16::MAX.next_up(), f16::INFINITY); + assert_f16_biteq!(f16::INFINITY.next_up(), f16::INFINITY); + + // Check that NaNs roundtrip. + let nan0 = f16::NAN; + let nan1 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK1); + let nan2 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK2); + assert_f16_biteq!(nan0.next_up(), nan0); + assert_f16_biteq!(nan1.next_up(), nan1); + assert_f16_biteq!(nan2.next_up(), nan2); +} + +#[test] +fn test_next_down() { + let tiny = f16::from_bits(TINY_BITS); + let tiny_up = f16::from_bits(TINY_UP_BITS); + let max_down = f16::from_bits(MAX_DOWN_BITS); + let largest_subnormal = f16::from_bits(LARGEST_SUBNORMAL_BITS); + let smallest_normal = f16::from_bits(SMALLEST_NORMAL_BITS); + assert_f16_biteq!(f16::NEG_INFINITY.next_down(), f16::NEG_INFINITY); + assert_f16_biteq!(f16::MIN.next_down(), f16::NEG_INFINITY); + assert_f16_biteq!((-max_down).next_down(), f16::MIN); + assert_f16_biteq!((-1.0f16).next_down(), -1.0 - f16::EPSILON); + assert_f16_biteq!((-largest_subnormal).next_down(), -smallest_normal); + assert_f16_biteq!((-tiny).next_down(), -tiny_up); + assert_f16_biteq!((-0.0f16).next_down(), -tiny); + assert_f16_biteq!((0.0f16).next_down(), -tiny); + assert_f16_biteq!(tiny.next_down(), 0.0f16); + assert_f16_biteq!(tiny_up.next_down(), tiny); + assert_f16_biteq!(smallest_normal.next_down(), largest_subnormal); + assert_f16_biteq!((1.0 + f16::EPSILON).next_down(), 1.0f16); + assert_f16_biteq!(f16::MAX.next_down(), max_down); + assert_f16_biteq!(f16::INFINITY.next_down(), f16::MAX); + + // Check that NaNs roundtrip. + let nan0 = f16::NAN; + let nan1 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK1); + let nan2 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK2); + assert_f16_biteq!(nan0.next_down(), nan0); + assert_f16_biteq!(nan1.next_down(), nan1); + assert_f16_biteq!(nan2.next_down(), nan2); +} + +#[test] +fn test_recip() { + let nan: f16 = f16::NAN; + let inf: f16 = f16::INFINITY; + let neg_inf: f16 = f16::NEG_INFINITY; + assert_eq!(1.0f16.recip(), 1.0); + assert_eq!(2.0f16.recip(), 0.5); + assert_eq!((-0.4f16).recip(), -2.5); + assert_eq!(0.0f16.recip(), inf); + assert!(nan.recip().is_nan()); + assert_eq!(inf.recip(), 0.0); + assert_eq!(neg_inf.recip(), 0.0); +} + +#[test] +fn test_to_degrees() { + let pi: f16 = consts::PI; + let nan: f16 = f16::NAN; + let inf: f16 = f16::INFINITY; + let neg_inf: f16 = f16::NEG_INFINITY; + assert_eq!(0.0f16.to_degrees(), 0.0); + assert_approx_eq!((-5.8f16).to_degrees(), -332.315521); + assert_approx_eq!(pi.to_degrees(), 180.0, F16_APPROX_L4); + assert!(nan.to_degrees().is_nan()); + assert_eq!(inf.to_degrees(), inf); + assert_eq!(neg_inf.to_degrees(), neg_inf); + assert_eq!(1_f16.to_degrees(), 57.2957795130823208767981548141051703); +} + +#[test] +fn test_to_radians() { + let pi: f16 = consts::PI; + let nan: f16 = f16::NAN; + let inf: f16 = f16::INFINITY; + let neg_inf: f16 = f16::NEG_INFINITY; + assert_eq!(0.0f16.to_radians(), 0.0); + assert_approx_eq!(154.6f16.to_radians(), 2.698279); + assert_approx_eq!((-332.31f16).to_radians(), -5.799903); + assert_approx_eq!(180.0f16.to_radians(), pi, F16_APPROX_L2); + assert!(nan.to_radians().is_nan()); + assert_eq!(inf.to_radians(), inf); + assert_eq!(neg_inf.to_radians(), neg_inf); +} + +#[test] +fn test_real_consts() { + // FIXME(f16_f128): add math tests when available + use super::consts; + + let pi: f16 = consts::PI; + let frac_pi_2: f16 = consts::FRAC_PI_2; + let frac_pi_3: f16 = consts::FRAC_PI_3; + let frac_pi_4: f16 = consts::FRAC_PI_4; + let frac_pi_6: f16 = consts::FRAC_PI_6; + let frac_pi_8: f16 = consts::FRAC_PI_8; + let frac_1_pi: f16 = consts::FRAC_1_PI; + let frac_2_pi: f16 = consts::FRAC_2_PI; + // let frac_2_sqrtpi: f16 = consts::FRAC_2_SQRT_PI; + // let sqrt2: f16 = consts::SQRT_2; + // let frac_1_sqrt2: f16 = consts::FRAC_1_SQRT_2; + // let e: f16 = consts::E; + // let log2_e: f16 = consts::LOG2_E; + // let log10_e: f16 = consts::LOG10_E; + // let ln_2: f16 = consts::LN_2; + // let ln_10: f16 = consts::LN_10; + + assert_approx_eq!(frac_pi_2, pi / 2f16); + assert_approx_eq!(frac_pi_3, pi / 3f16); + assert_approx_eq!(frac_pi_4, pi / 4f16); + assert_approx_eq!(frac_pi_6, pi / 6f16); + assert_approx_eq!(frac_pi_8, pi / 8f16); + assert_approx_eq!(frac_1_pi, 1f16 / pi); + assert_approx_eq!(frac_2_pi, 2f16 / pi); + // assert_approx_eq!(frac_2_sqrtpi, 2f16 / pi.sqrt()); + // assert_approx_eq!(sqrt2, 2f16.sqrt()); + // assert_approx_eq!(frac_1_sqrt2, 1f16 / 2f16.sqrt()); + // assert_approx_eq!(log2_e, e.log2()); + // assert_approx_eq!(log10_e, e.log10()); + // assert_approx_eq!(ln_2, 2f16.ln()); + // assert_approx_eq!(ln_10, 10f16.ln()); +} + +#[test] +fn test_float_bits_conv() { + assert_eq!((1f16).to_bits(), 0x3c00); + assert_eq!((12.5f16).to_bits(), 0x4a40); + assert_eq!((1337f16).to_bits(), 0x6539); + assert_eq!((-14.25f16).to_bits(), 0xcb20); + assert_approx_eq!(f16::from_bits(0x3c00), 1.0); + assert_approx_eq!(f16::from_bits(0x4a40), 12.5); + assert_approx_eq!(f16::from_bits(0x6539), 1337.0); + assert_approx_eq!(f16::from_bits(0xcb20), -14.25); + + // Check that NaNs roundtrip their bits regardless of signaling-ness + let masked_nan1 = f16::NAN.to_bits() ^ NAN_MASK1; + let masked_nan2 = f16::NAN.to_bits() ^ NAN_MASK2; + assert!(f16::from_bits(masked_nan1).is_nan()); + assert!(f16::from_bits(masked_nan2).is_nan()); + + assert_eq!(f16::from_bits(masked_nan1).to_bits(), masked_nan1); + assert_eq!(f16::from_bits(masked_nan2).to_bits(), masked_nan2); +} + +#[test] +#[should_panic] +fn test_clamp_min_greater_than_max() { + let _ = 1.0f16.clamp(3.0, 1.0); +} + +#[test] +#[should_panic] +fn test_clamp_min_is_nan() { + let _ = 1.0f16.clamp(f16::NAN, 1.0); +} + +#[test] +#[should_panic] +fn test_clamp_max_is_nan() { + let _ = 1.0f16.clamp(3.0, f16::NAN); +} + +#[test] +fn test_total_cmp() { + use core::cmp::Ordering; + + fn quiet_bit_mask() -> u16 { + 1 << (f16::MANTISSA_DIGITS - 2) + } + + // FIXME(f16_f128): test subnormals when powf is available + // fn min_subnorm() -> f16 { + // f16::MIN_POSITIVE / f16::powf(2.0, f16::MANTISSA_DIGITS as f16 - 1.0) + // } + + // fn max_subnorm() -> f16 { + // f16::MIN_POSITIVE - min_subnorm() + // } + + fn q_nan() -> f16 { + f16::from_bits(f16::NAN.to_bits() | quiet_bit_mask()) + } + + fn s_nan() -> f16 { + f16::from_bits((f16::NAN.to_bits() & !quiet_bit_mask()) + 42) + } + + assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan())); + assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan())); + assert_eq!(Ordering::Equal, (-f16::INFINITY).total_cmp(&-f16::INFINITY)); + assert_eq!(Ordering::Equal, (-f16::MAX).total_cmp(&-f16::MAX)); + assert_eq!(Ordering::Equal, (-2.5_f16).total_cmp(&-2.5)); + assert_eq!(Ordering::Equal, (-1.0_f16).total_cmp(&-1.0)); + assert_eq!(Ordering::Equal, (-1.5_f16).total_cmp(&-1.5)); + assert_eq!(Ordering::Equal, (-0.5_f16).total_cmp(&-0.5)); + assert_eq!(Ordering::Equal, (-f16::MIN_POSITIVE).total_cmp(&-f16::MIN_POSITIVE)); + // assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Equal, (-0.0_f16).total_cmp(&-0.0)); + assert_eq!(Ordering::Equal, 0.0_f16.total_cmp(&0.0)); + // assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm())); + assert_eq!(Ordering::Equal, f16::MIN_POSITIVE.total_cmp(&f16::MIN_POSITIVE)); + assert_eq!(Ordering::Equal, 0.5_f16.total_cmp(&0.5)); + assert_eq!(Ordering::Equal, 1.0_f16.total_cmp(&1.0)); + assert_eq!(Ordering::Equal, 1.5_f16.total_cmp(&1.5)); + assert_eq!(Ordering::Equal, 2.5_f16.total_cmp(&2.5)); + assert_eq!(Ordering::Equal, f16::MAX.total_cmp(&f16::MAX)); + assert_eq!(Ordering::Equal, f16::INFINITY.total_cmp(&f16::INFINITY)); + assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan())); + assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan())); + + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::INFINITY)); + assert_eq!(Ordering::Less, (-f16::INFINITY).total_cmp(&-f16::MAX)); + assert_eq!(Ordering::Less, (-f16::MAX).total_cmp(&-2.5)); + assert_eq!(Ordering::Less, (-2.5_f16).total_cmp(&-1.5)); + assert_eq!(Ordering::Less, (-1.5_f16).total_cmp(&-1.0)); + assert_eq!(Ordering::Less, (-1.0_f16).total_cmp(&-0.5)); + assert_eq!(Ordering::Less, (-0.5_f16).total_cmp(&-f16::MIN_POSITIVE)); + // assert_eq!(Ordering::Less, (-f16::MIN_POSITIVE).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm())); + // assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0)); + assert_eq!(Ordering::Less, (-0.0_f16).total_cmp(&0.0)); + // assert_eq!(Ordering::Less, 0.0_f16.total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm())); + // assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f16::MIN_POSITIVE)); + assert_eq!(Ordering::Less, f16::MIN_POSITIVE.total_cmp(&0.5)); + assert_eq!(Ordering::Less, 0.5_f16.total_cmp(&1.0)); + assert_eq!(Ordering::Less, 1.0_f16.total_cmp(&1.5)); + assert_eq!(Ordering::Less, 1.5_f16.total_cmp(&2.5)); + assert_eq!(Ordering::Less, 2.5_f16.total_cmp(&f16::MAX)); + assert_eq!(Ordering::Less, f16::MAX.total_cmp(&f16::INFINITY)); + assert_eq!(Ordering::Less, f16::INFINITY.total_cmp(&s_nan())); + assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan())); + + assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan())); + assert_eq!(Ordering::Greater, (-f16::INFINITY).total_cmp(&-s_nan())); + assert_eq!(Ordering::Greater, (-f16::MAX).total_cmp(&-f16::INFINITY)); + assert_eq!(Ordering::Greater, (-2.5_f16).total_cmp(&-f16::MAX)); + assert_eq!(Ordering::Greater, (-1.5_f16).total_cmp(&-2.5)); + assert_eq!(Ordering::Greater, (-1.0_f16).total_cmp(&-1.5)); + assert_eq!(Ordering::Greater, (-0.5_f16).total_cmp(&-1.0)); + assert_eq!(Ordering::Greater, (-f16::MIN_POSITIVE).total_cmp(&-0.5)); + // assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f16::MIN_POSITIVE)); + // assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Greater, (-0.0_f16).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Greater, 0.0_f16.total_cmp(&-0.0)); + // assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0)); + // assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Greater, f16::MIN_POSITIVE.total_cmp(&max_subnorm())); + assert_eq!(Ordering::Greater, 0.5_f16.total_cmp(&f16::MIN_POSITIVE)); + assert_eq!(Ordering::Greater, 1.0_f16.total_cmp(&0.5)); + assert_eq!(Ordering::Greater, 1.5_f16.total_cmp(&1.0)); + assert_eq!(Ordering::Greater, 2.5_f16.total_cmp(&1.5)); + assert_eq!(Ordering::Greater, f16::MAX.total_cmp(&2.5)); + assert_eq!(Ordering::Greater, f16::INFINITY.total_cmp(&f16::MAX)); + assert_eq!(Ordering::Greater, s_nan().total_cmp(&f16::INFINITY)); + assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan())); + + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f16::INFINITY)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f16::MAX)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f16::MIN_POSITIVE)); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0)); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm())); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f16::MIN_POSITIVE)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f16::MAX)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f16::INFINITY)); + assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan())); + + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::INFINITY)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::MAX)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::MIN_POSITIVE)); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm())); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm())); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0)); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm())); + // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm())); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f16::MIN_POSITIVE)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f16::MAX)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f16::INFINITY)); + assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); +} diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 58df83bd79d23..972b6015932db 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -373,10 +373,17 @@ macro_rules! dbg { }; } +/// Verify that floats are within a tolerance of each other, 1.0e-6 by default. #[cfg(test)] macro_rules! assert_approx_eq { - ($a:expr, $b:expr) => {{ + ($a:expr, $b:expr) => {{ assert_approx_eq!($a, $b, 1.0e-6) }}; + ($a:expr, $b:expr, $lim:expr) => {{ let (a, b) = (&$a, &$b); - assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b); + let diff = (*a - *b).abs(); + assert!( + diff < $lim, + "{a:?} is not approximately equal to {b:?} (threshold {lim:?}, actual {diff:?})", + lim = $lim + ); }}; }