Skip to content

Commit e74b992

Browse files
committed
Auto merge of #127020 - tgross35:f16-f128-classify, r=workingjubilee
Add classify and related methods for f16 and f128 Also constify some functions where that was blocked on classify being available. r? libs
2 parents 0da95bd + 421ca1a commit e74b992

File tree

4 files changed

+583
-71
lines changed

4 files changed

+583
-71
lines changed

library/core/src/num/f128.rs

+224-16
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
#![unstable(feature = "f128", issue = "116909")]
1313

1414
use crate::convert::FloatToInt;
15+
#[cfg(not(test))]
16+
use crate::intrinsics;
1517
use crate::mem;
18+
use crate::num::FpCategory;
1619

1720
/// Basic mathematical constants.
1821
#[unstable(feature = "f128", issue = "116909")]
@@ -251,6 +254,12 @@ impl f128 {
251254
#[cfg(not(bootstrap))]
252255
pub(crate) const SIGN_MASK: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000;
253256

257+
/// Exponent mask
258+
pub(crate) const EXP_MASK: u128 = 0x7fff_0000_0000_0000_0000_0000_0000_0000;
259+
260+
/// Mantissa mask
261+
pub(crate) const MAN_MASK: u128 = 0x0000_ffff_ffff_ffff_ffff_ffff_ffff_ffff;
262+
254263
/// Minimum representable positive value (min subnormal)
255264
#[cfg(not(bootstrap))]
256265
const TINY_BITS: u128 = 0x1;
@@ -354,6 +363,119 @@ impl f128 {
354363
self.abs_private() < Self::INFINITY
355364
}
356365

366+
/// Returns `true` if the number is [subnormal].
367+
///
368+
/// ```
369+
/// #![feature(f128)]
370+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
371+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
372+
///
373+
/// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
374+
/// let max = f128::MAX;
375+
/// let lower_than_min = 1.0e-4960_f128;
376+
/// let zero = 0.0_f128;
377+
///
378+
/// assert!(!min.is_subnormal());
379+
/// assert!(!max.is_subnormal());
380+
///
381+
/// assert!(!zero.is_subnormal());
382+
/// assert!(!f128::NAN.is_subnormal());
383+
/// assert!(!f128::INFINITY.is_subnormal());
384+
/// // Values between `0` and `min` are Subnormal.
385+
/// assert!(lower_than_min.is_subnormal());
386+
/// # }
387+
/// ```
388+
///
389+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
390+
#[inline]
391+
#[must_use]
392+
#[cfg(not(bootstrap))]
393+
#[unstable(feature = "f128", issue = "116909")]
394+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
395+
pub const fn is_subnormal(self) -> bool {
396+
matches!(self.classify(), FpCategory::Subnormal)
397+
}
398+
399+
/// Returns `true` if the number is neither zero, infinite, [subnormal], or NaN.
400+
///
401+
/// ```
402+
/// #![feature(f128)]
403+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
404+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
405+
///
406+
/// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
407+
/// let max = f128::MAX;
408+
/// let lower_than_min = 1.0e-4960_f128;
409+
/// let zero = 0.0_f128;
410+
///
411+
/// assert!(min.is_normal());
412+
/// assert!(max.is_normal());
413+
///
414+
/// assert!(!zero.is_normal());
415+
/// assert!(!f128::NAN.is_normal());
416+
/// assert!(!f128::INFINITY.is_normal());
417+
/// // Values between `0` and `min` are Subnormal.
418+
/// assert!(!lower_than_min.is_normal());
419+
/// # }
420+
/// ```
421+
///
422+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
423+
#[inline]
424+
#[must_use]
425+
#[cfg(not(bootstrap))]
426+
#[unstable(feature = "f128", issue = "116909")]
427+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
428+
pub const fn is_normal(self) -> bool {
429+
matches!(self.classify(), FpCategory::Normal)
430+
}
431+
432+
/// Returns the floating point category of the number. If only one property
433+
/// is going to be tested, it is generally faster to use the specific
434+
/// predicate instead.
435+
///
436+
/// ```
437+
/// #![feature(f128)]
438+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
439+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
440+
///
441+
/// use std::num::FpCategory;
442+
///
443+
/// let num = 12.4_f128;
444+
/// let inf = f128::INFINITY;
445+
///
446+
/// assert_eq!(num.classify(), FpCategory::Normal);
447+
/// assert_eq!(inf.classify(), FpCategory::Infinite);
448+
/// # }
449+
/// ```
450+
#[inline]
451+
#[cfg(not(bootstrap))]
452+
#[unstable(feature = "f128", issue = "116909")]
453+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
454+
pub const fn classify(self) -> FpCategory {
455+
// Other float types cannot use a bitwise classify because they may suffer a variety
456+
// of errors if the backend chooses to cast to different float types (x87). `f128` cannot
457+
// fit into any other float types so this is not a concern, and we rely on bit patterns.
458+
459+
// SAFETY: POD bitcast, same as in `to_bits`.
460+
let bits = unsafe { mem::transmute::<f128, u128>(self) };
461+
Self::classify_bits(bits)
462+
}
463+
464+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
465+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
466+
/// plus a transmute. We do not live in a just world, but we can make it more so.
467+
#[inline]
468+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
469+
const fn classify_bits(b: u128) -> FpCategory {
470+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
471+
(0, Self::EXP_MASK) => FpCategory::Infinite,
472+
(_, Self::EXP_MASK) => FpCategory::Nan,
473+
(0, 0) => FpCategory::Zero,
474+
(_, 0) => FpCategory::Subnormal,
475+
_ => FpCategory::Normal,
476+
}
477+
}
478+
357479
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
358480
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
359481
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
@@ -638,12 +760,52 @@ impl f128 {
638760
/// ```
639761
#[inline]
640762
#[unstable(feature = "f128", issue = "116909")]
763+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
641764
#[must_use = "this returns the result of the operation, without modifying the original"]
642-
pub fn to_bits(self) -> u128 {
643-
// SAFETY: `u128` is a plain old datatype so we can always... uh...
644-
// ...look, just pretend you forgot what you just read.
645-
// Stability concerns.
646-
unsafe { mem::transmute(self) }
765+
pub const fn to_bits(self) -> u128 {
766+
// SAFETY: `u128` is a plain old datatype so we can always transmute to it.
767+
// ...sorta.
768+
//
769+
// It turns out that at runtime, it is possible for a floating point number
770+
// to be subject to a floating point mode that alters nonzero subnormal numbers
771+
// to zero on reads and writes, aka "denormals are zero" and "flush to zero".
772+
//
773+
// And, of course evaluating to a NaN value is fairly nondeterministic.
774+
// More precisely: when NaN should be returned is knowable, but which NaN?
775+
// So far that's defined by a combination of LLVM and the CPU, not Rust.
776+
// This function, however, allows observing the bitstring of a NaN,
777+
// thus introspection on CTFE.
778+
//
779+
// In order to preserve, at least for the moment, const-to-runtime equivalence,
780+
// we reject any of these possible situations from happening.
781+
#[inline]
782+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
783+
const fn ct_f128_to_u128(ct: f128) -> u128 {
784+
// FIXME(f16_f128): we should use `.classify()` like `f32` and `f64`, but that
785+
// is not available on all platforms (needs `netf2` and `unordtf2`). So classify
786+
// the bits instead.
787+
788+
// SAFETY: this is a POD transmutation
789+
let bits = unsafe { mem::transmute::<f128, u128>(ct) };
790+
match f128::classify_bits(bits) {
791+
FpCategory::Nan => {
792+
panic!("const-eval error: cannot use f128::to_bits on a NaN")
793+
}
794+
FpCategory::Subnormal => {
795+
panic!("const-eval error: cannot use f128::to_bits on a subnormal number")
796+
}
797+
FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => bits,
798+
}
799+
}
800+
801+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
802+
fn rt_f128_to_u128(x: f128) -> u128 {
803+
// SAFETY: `u128` is a plain old datatype so we can always... uh...
804+
// ...look, just pretend you forgot what you just read.
805+
// Stability concerns.
806+
unsafe { mem::transmute(x) }
807+
}
808+
intrinsics::const_eval_select((self,), ct_f128_to_u128, rt_f128_to_u128)
647809
}
648810

649811
/// Raw transmutation from `u128`.
@@ -688,11 +850,51 @@ impl f128 {
688850
#[inline]
689851
#[must_use]
690852
#[unstable(feature = "f128", issue = "116909")]
691-
pub fn from_bits(v: u128) -> Self {
692-
// SAFETY: `u128 is a plain old datatype so we can always... uh...
693-
// ...look, just pretend you forgot what you just read.
694-
// Stability concerns.
695-
unsafe { mem::transmute(v) }
853+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
854+
pub const fn from_bits(v: u128) -> Self {
855+
// It turns out the safety issues with sNaN were overblown! Hooray!
856+
// SAFETY: `u128` is a plain old datatype so we can always transmute from it
857+
// ...sorta.
858+
//
859+
// It turns out that at runtime, it is possible for a floating point number
860+
// to be subject to floating point modes that alter nonzero subnormal numbers
861+
// to zero on reads and writes, aka "denormals are zero" and "flush to zero".
862+
// This is not a problem usually, but at least one tier2 platform for Rust
863+
// actually exhibits this behavior by default: thumbv7neon
864+
// aka "the Neon FPU in AArch32 state"
865+
//
866+
// And, of course evaluating to a NaN value is fairly nondeterministic.
867+
// More precisely: when NaN should be returned is knowable, but which NaN?
868+
// So far that's defined by a combination of LLVM and the CPU, not Rust.
869+
// This function, however, allows observing the bitstring of a NaN,
870+
// thus introspection on CTFE.
871+
//
872+
// In order to preserve, at least for the moment, const-to-runtime equivalence,
873+
// reject any of these possible situations from happening.
874+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
875+
const fn ct_u128_to_f128(ct: u128) -> f128 {
876+
match f128::classify_bits(ct) {
877+
FpCategory::Subnormal => {
878+
panic!("const-eval error: cannot use f128::from_bits on a subnormal number")
879+
}
880+
FpCategory::Nan => {
881+
panic!("const-eval error: cannot use f128::from_bits on NaN")
882+
}
883+
FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => {
884+
// SAFETY: It's not a frumious number
885+
unsafe { mem::transmute::<u128, f128>(ct) }
886+
}
887+
}
888+
}
889+
890+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
891+
fn rt_u128_to_f128(x: u128) -> f128 {
892+
// SAFETY: `u128` is a plain old datatype so we can always... uh...
893+
// ...look, just pretend you forgot what you just read.
894+
// Stability concerns.
895+
unsafe { mem::transmute(x) }
896+
}
897+
intrinsics::const_eval_select((v,), ct_u128_to_f128, rt_u128_to_f128)
696898
}
697899

698900
/// Return the memory representation of this floating point number as a byte array in
@@ -715,8 +917,9 @@ impl f128 {
715917
/// ```
716918
#[inline]
717919
#[unstable(feature = "f128", issue = "116909")]
920+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
718921
#[must_use = "this returns the result of the operation, without modifying the original"]
719-
pub fn to_be_bytes(self) -> [u8; 16] {
922+
pub const fn to_be_bytes(self) -> [u8; 16] {
720923
self.to_bits().to_be_bytes()
721924
}
722925

@@ -740,8 +943,9 @@ impl f128 {
740943
/// ```
741944
#[inline]
742945
#[unstable(feature = "f128", issue = "116909")]
946+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
743947
#[must_use = "this returns the result of the operation, without modifying the original"]
744-
pub fn to_le_bytes(self) -> [u8; 16] {
948+
pub const fn to_le_bytes(self) -> [u8; 16] {
745949
self.to_bits().to_le_bytes()
746950
}
747951

@@ -776,8 +980,9 @@ impl f128 {
776980
/// ```
777981
#[inline]
778982
#[unstable(feature = "f128", issue = "116909")]
983+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
779984
#[must_use = "this returns the result of the operation, without modifying the original"]
780-
pub fn to_ne_bytes(self) -> [u8; 16] {
985+
pub const fn to_ne_bytes(self) -> [u8; 16] {
781986
self.to_bits().to_ne_bytes()
782987
}
783988

@@ -803,7 +1008,8 @@ impl f128 {
8031008
#[inline]
8041009
#[must_use]
8051010
#[unstable(feature = "f128", issue = "116909")]
806-
pub fn from_be_bytes(bytes: [u8; 16]) -> Self {
1011+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
1012+
pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
8071013
Self::from_bits(u128::from_be_bytes(bytes))
8081014
}
8091015

@@ -829,7 +1035,8 @@ impl f128 {
8291035
#[inline]
8301036
#[must_use]
8311037
#[unstable(feature = "f128", issue = "116909")]
832-
pub fn from_le_bytes(bytes: [u8; 16]) -> Self {
1038+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
1039+
pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
8331040
Self::from_bits(u128::from_le_bytes(bytes))
8341041
}
8351042

@@ -865,7 +1072,8 @@ impl f128 {
8651072
#[inline]
8661073
#[must_use]
8671074
#[unstable(feature = "f128", issue = "116909")]
868-
pub fn from_ne_bytes(bytes: [u8; 16]) -> Self {
1075+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
1076+
pub const fn from_ne_bytes(bytes: [u8; 16]) -> Self {
8691077
Self::from_bits(u128::from_ne_bytes(bytes))
8701078
}
8711079

0 commit comments

Comments
 (0)