Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ac443f2

Browse files
committedJul 15, 2024
Auto merge of rust-lang#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 eae9451 + 14c24b1 commit ac443f2

File tree

4 files changed

+585
-71
lines changed

4 files changed

+585
-71
lines changed
 

‎core/src/num/f128.rs

+225-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,52 @@ 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+
#[inline]
875+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
876+
const fn ct_u128_to_f128(ct: u128) -> f128 {
877+
match f128::classify_bits(ct) {
878+
FpCategory::Subnormal => {
879+
panic!("const-eval error: cannot use f128::from_bits on a subnormal number")
880+
}
881+
FpCategory::Nan => {
882+
panic!("const-eval error: cannot use f128::from_bits on NaN")
883+
}
884+
FpCategory::Infinite | FpCategory::Normal | FpCategory::Zero => {
885+
// SAFETY: It's not a frumious number
886+
unsafe { mem::transmute::<u128, f128>(ct) }
887+
}
888+
}
889+
}
890+
891+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
892+
fn rt_u128_to_f128(x: u128) -> f128 {
893+
// SAFETY: `u128` is a plain old datatype so we can always... uh...
894+
// ...look, just pretend you forgot what you just read.
895+
// Stability concerns.
896+
unsafe { mem::transmute(x) }
897+
}
898+
intrinsics::const_eval_select((v,), ct_u128_to_f128, rt_u128_to_f128)
696899
}
697900

698901
/// Return the memory representation of this floating point number as a byte array in
@@ -715,8 +918,9 @@ impl f128 {
715918
/// ```
716919
#[inline]
717920
#[unstable(feature = "f128", issue = "116909")]
921+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
718922
#[must_use = "this returns the result of the operation, without modifying the original"]
719-
pub fn to_be_bytes(self) -> [u8; 16] {
923+
pub const fn to_be_bytes(self) -> [u8; 16] {
720924
self.to_bits().to_be_bytes()
721925
}
722926

@@ -740,8 +944,9 @@ impl f128 {
740944
/// ```
741945
#[inline]
742946
#[unstable(feature = "f128", issue = "116909")]
947+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
743948
#[must_use = "this returns the result of the operation, without modifying the original"]
744-
pub fn to_le_bytes(self) -> [u8; 16] {
949+
pub const fn to_le_bytes(self) -> [u8; 16] {
745950
self.to_bits().to_le_bytes()
746951
}
747952

@@ -776,8 +981,9 @@ impl f128 {
776981
/// ```
777982
#[inline]
778983
#[unstable(feature = "f128", issue = "116909")]
984+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
779985
#[must_use = "this returns the result of the operation, without modifying the original"]
780-
pub fn to_ne_bytes(self) -> [u8; 16] {
986+
pub const fn to_ne_bytes(self) -> [u8; 16] {
781987
self.to_bits().to_ne_bytes()
782988
}
783989

@@ -803,7 +1009,8 @@ impl f128 {
8031009
#[inline]
8041010
#[must_use]
8051011
#[unstable(feature = "f128", issue = "116909")]
806-
pub fn from_be_bytes(bytes: [u8; 16]) -> Self {
1012+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
1013+
pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
8071014
Self::from_bits(u128::from_be_bytes(bytes))
8081015
}
8091016

@@ -829,7 +1036,8 @@ impl f128 {
8291036
#[inline]
8301037
#[must_use]
8311038
#[unstable(feature = "f128", issue = "116909")]
832-
pub fn from_le_bytes(bytes: [u8; 16]) -> Self {
1039+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
1040+
pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
8331041
Self::from_bits(u128::from_le_bytes(bytes))
8341042
}
8351043

@@ -865,7 +1073,8 @@ impl f128 {
8651073
#[inline]
8661074
#[must_use]
8671075
#[unstable(feature = "f128", issue = "116909")]
868-
pub fn from_ne_bytes(bytes: [u8; 16]) -> Self {
1076+
#[rustc_const_unstable(feature = "const_float_bits_conv", issue = "72447")]
1077+
pub const fn from_ne_bytes(bytes: [u8; 16]) -> Self {
8691078
Self::from_bits(u128::from_ne_bytes(bytes))
8701079
}
8711080

There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.