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 f710e38

Browse files
committedJul 14, 2024
Add classify and related methods for f16 and f128
1 parent c5ab1f0 commit f710e38

File tree

4 files changed

+375
-39
lines changed

4 files changed

+375
-39
lines changed
 

‎core/src/num/f128.rs

+120
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use crate::convert::FloatToInt;
1515
use crate::mem;
16+
use crate::num::FpCategory;
1617

1718
/// Basic mathematical constants.
1819
#[unstable(feature = "f128", issue = "116909")]
@@ -251,6 +252,12 @@ impl f128 {
251252
#[cfg(not(bootstrap))]
252253
pub(crate) const SIGN_MASK: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000;
253254

255+
/// Exponent mask
256+
pub(crate) const EXP_MASK: u128 = 0x7fff_0000_0000_0000_0000_0000_0000_0000;
257+
258+
/// Mantissa mask
259+
pub(crate) const MAN_MASK: u128 = 0x0000_ffff_ffff_ffff_ffff_ffff_ffff_ffff;
260+
254261
/// Minimum representable positive value (min subnormal)
255262
#[cfg(not(bootstrap))]
256263
const TINY_BITS: u128 = 0x1;
@@ -354,6 +361,119 @@ impl f128 {
354361
self.abs_private() < Self::INFINITY
355362
}
356363

364+
/// Returns `true` if the number is [subnormal].
365+
///
366+
/// ```
367+
/// #![feature(f128)]
368+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
369+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
370+
///
371+
/// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
372+
/// let max = f128::MAX;
373+
/// let lower_than_min = 1.0e-4960_f128;
374+
/// let zero = 0.0_f128;
375+
///
376+
/// assert!(!min.is_subnormal());
377+
/// assert!(!max.is_subnormal());
378+
///
379+
/// assert!(!zero.is_subnormal());
380+
/// assert!(!f128::NAN.is_subnormal());
381+
/// assert!(!f128::INFINITY.is_subnormal());
382+
/// // Values between `0` and `min` are Subnormal.
383+
/// assert!(lower_than_min.is_subnormal());
384+
/// # }
385+
/// ```
386+
///
387+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
388+
#[inline]
389+
#[must_use]
390+
#[cfg(not(bootstrap))]
391+
#[unstable(feature = "f128", issue = "116909")]
392+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
393+
pub const fn is_subnormal(self) -> bool {
394+
matches!(self.classify(), FpCategory::Subnormal)
395+
}
396+
397+
/// Returns `true` if the number is neither zero, infinite, [subnormal], or NaN.
398+
///
399+
/// ```
400+
/// #![feature(f128)]
401+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
402+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
403+
///
404+
/// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
405+
/// let max = f128::MAX;
406+
/// let lower_than_min = 1.0e-4960_f128;
407+
/// let zero = 0.0_f128;
408+
///
409+
/// assert!(min.is_normal());
410+
/// assert!(max.is_normal());
411+
///
412+
/// assert!(!zero.is_normal());
413+
/// assert!(!f128::NAN.is_normal());
414+
/// assert!(!f128::INFINITY.is_normal());
415+
/// // Values between `0` and `min` are Subnormal.
416+
/// assert!(!lower_than_min.is_normal());
417+
/// # }
418+
/// ```
419+
///
420+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
421+
#[inline]
422+
#[must_use]
423+
#[cfg(not(bootstrap))]
424+
#[unstable(feature = "f128", issue = "116909")]
425+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
426+
pub const fn is_normal(self) -> bool {
427+
matches!(self.classify(), FpCategory::Normal)
428+
}
429+
430+
/// Returns the floating point category of the number. If only one property
431+
/// is going to be tested, it is generally faster to use the specific
432+
/// predicate instead.
433+
///
434+
/// ```
435+
/// #![feature(f128)]
436+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
437+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
438+
///
439+
/// use std::num::FpCategory;
440+
///
441+
/// let num = 12.4_f128;
442+
/// let inf = f128::INFINITY;
443+
///
444+
/// assert_eq!(num.classify(), FpCategory::Normal);
445+
/// assert_eq!(inf.classify(), FpCategory::Infinite);
446+
/// # }
447+
/// ```
448+
#[inline]
449+
#[cfg(not(bootstrap))]
450+
#[unstable(feature = "f128", issue = "116909")]
451+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
452+
pub const fn classify(self) -> FpCategory {
453+
// Other float types cannot use a bitwise classify because they may suffer a variety
454+
// of errors if the backend chooses to cast to different float types (x87). `f128` cannot
455+
// fit into any other float types so this is not a concern, and we rely on bit patterns.
456+
457+
// SAFETY: POD bitcast, same as in `to_bits`.
458+
let bits = unsafe { mem::transmute::<f128, u128>(self) };
459+
Self::classify_bits(bits)
460+
}
461+
462+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
463+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
464+
/// plus a transmute. We do not live in a just world, but we can make it more so.
465+
#[inline]
466+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
467+
const fn classify_bits(b: u128) -> FpCategory {
468+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
469+
(0, Self::EXP_MASK) => FpCategory::Infinite,
470+
(_, Self::EXP_MASK) => FpCategory::Nan,
471+
(0, 0) => FpCategory::Zero,
472+
(_, 0) => FpCategory::Subnormal,
473+
_ => FpCategory::Normal,
474+
}
475+
}
476+
357477
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
358478
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
359479
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that

‎core/src/num/f16.rs

+161-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use crate::convert::FloatToInt;
1515
use crate::mem;
16+
use crate::num::FpCategory;
1617

1718
/// Basic mathematical constants.
1819
#[unstable(feature = "f16", issue = "116909")]
@@ -244,7 +245,13 @@ impl f16 {
244245

245246
/// Sign bit
246247
#[cfg(not(bootstrap))]
247-
const SIGN_MASK: u16 = 0x8000;
248+
pub(crate) const SIGN_MASK: u16 = 0x8000;
249+
250+
/// Exponent mask
251+
pub(crate) const EXP_MASK: u16 = 0x7c00;
252+
253+
/// Mantissa mask
254+
pub(crate) const MAN_MASK: u16 = 0x03ff;
248255

249256
/// Minimum representable positive value (min subnormal)
250257
#[cfg(not(bootstrap))]
@@ -344,6 +351,159 @@ impl f16 {
344351
self.abs_private() < Self::INFINITY
345352
}
346353

354+
/// Returns `true` if the number is [subnormal].
355+
///
356+
/// ```
357+
/// #![feature(f16)]
358+
/// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
359+
///
360+
/// let min = f16::MIN_POSITIVE; // 6.1035e-5
361+
/// let max = f16::MAX;
362+
/// let lower_than_min = 1.0e-7_f16;
363+
/// let zero = 0.0_f16;
364+
///
365+
/// assert!(!min.is_subnormal());
366+
/// assert!(!max.is_subnormal());
367+
///
368+
/// assert!(!zero.is_subnormal());
369+
/// assert!(!f16::NAN.is_subnormal());
370+
/// assert!(!f16::INFINITY.is_subnormal());
371+
/// // Values between `0` and `min` are Subnormal.
372+
/// assert!(lower_than_min.is_subnormal());
373+
/// # }
374+
/// ```
375+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
376+
#[inline]
377+
#[must_use]
378+
#[cfg(not(bootstrap))]
379+
#[unstable(feature = "f16", issue = "116909")]
380+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
381+
pub const fn is_subnormal(self) -> bool {
382+
matches!(self.classify(), FpCategory::Subnormal)
383+
}
384+
385+
/// Returns `true` if the number is neither zero, infinite, [subnormal], or NaN.
386+
///
387+
/// ```
388+
/// #![feature(f16)]
389+
/// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
390+
///
391+
/// let min = f16::MIN_POSITIVE; // 6.1035e-5
392+
/// let max = f16::MAX;
393+
/// let lower_than_min = 1.0e-7_f16;
394+
/// let zero = 0.0_f16;
395+
///
396+
/// assert!(min.is_normal());
397+
/// assert!(max.is_normal());
398+
///
399+
/// assert!(!zero.is_normal());
400+
/// assert!(!f16::NAN.is_normal());
401+
/// assert!(!f16::INFINITY.is_normal());
402+
/// // Values between `0` and `min` are Subnormal.
403+
/// assert!(!lower_than_min.is_normal());
404+
/// # }
405+
/// ```
406+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
407+
#[inline]
408+
#[must_use]
409+
#[cfg(not(bootstrap))]
410+
#[unstable(feature = "f16", issue = "116909")]
411+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
412+
pub const fn is_normal(self) -> bool {
413+
matches!(self.classify(), FpCategory::Normal)
414+
}
415+
416+
/// Returns the floating point category of the number. If only one property
417+
/// is going to be tested, it is generally faster to use the specific
418+
/// predicate instead.
419+
///
420+
/// ```
421+
/// #![feature(f16)]
422+
/// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
423+
///
424+
/// use std::num::FpCategory;
425+
///
426+
/// let num = 12.4_f16;
427+
/// let inf = f16::INFINITY;
428+
///
429+
/// assert_eq!(num.classify(), FpCategory::Normal);
430+
/// assert_eq!(inf.classify(), FpCategory::Infinite);
431+
/// # }
432+
/// ```
433+
#[inline]
434+
#[cfg(not(bootstrap))]
435+
#[unstable(feature = "f16", issue = "116909")]
436+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
437+
pub const fn classify(self) -> FpCategory {
438+
// A previous implementation for f32/f64 tried to only use bitmask-based checks,
439+
// using `to_bits` to transmute the float to its bit repr and match on that.
440+
// Unfortunately, floating point numbers can be much worse than that.
441+
// This also needs to not result in recursive evaluations of `to_bits`.
442+
//
443+
444+
// Platforms without native support generally convert to `f32` to perform operations,
445+
// and most of these platforms correctly round back to `f16` after each operation.
446+
// However, some platforms have bugs where they keep the excess `f32` precision (e.g.
447+
// WASM, see llvm/llvm-project#96437). This implementation makes a best-effort attempt
448+
// to account for that excess precision.
449+
if self.is_infinite() {
450+
// Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
451+
FpCategory::Infinite
452+
} else if self.is_nan() {
453+
// And it may not be NaN, as it can simply be an "overextended" finite value.
454+
FpCategory::Nan
455+
} else {
456+
// However, std can't simply compare to zero to check for zero, either,
457+
// as correctness requires avoiding equality tests that may be Subnormal == -0.0
458+
// because it may be wrong under "denormals are zero" and "flush to zero" modes.
459+
// Most of std's targets don't use those, but they are used for thumbv7neon.
460+
// So, this does use bitpattern matching for the rest.
461+
462+
// SAFETY: f16 to u16 is fine. Usually.
463+
// If classify has gotten this far, the value is definitely in one of these categories.
464+
unsafe { f16::partial_classify(self) }
465+
}
466+
}
467+
468+
/// This doesn't actually return a right answer for NaN on purpose,
469+
/// seeing as how it cannot correctly discern between a floating point NaN,
470+
/// and some normal floating point numbers truncated from an x87 FPU.
471+
///
472+
/// # Safety
473+
///
474+
/// This requires making sure you call this function for values it answers correctly on,
475+
/// otherwise it returns a wrong answer. This is not important for memory safety per se,
476+
/// but getting floats correct is important for not accidentally leaking const eval
477+
/// runtime-deviating logic which may or may not be acceptable.
478+
#[inline]
479+
#[cfg(not(bootstrap))]
480+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
481+
const unsafe fn partial_classify(self) -> FpCategory {
482+
// SAFETY: The caller is not asking questions for which this will tell lies.
483+
let b = unsafe { mem::transmute::<f16, u16>(self) };
484+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
485+
(0, Self::EXP_MASK) => FpCategory::Infinite,
486+
(0, 0) => FpCategory::Zero,
487+
(_, 0) => FpCategory::Subnormal,
488+
_ => FpCategory::Normal,
489+
}
490+
}
491+
492+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
493+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
494+
/// plus a transmute. We do not live in a just world, but we can make it more so.
495+
#[inline]
496+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
497+
const fn classify_bits(b: u16) -> FpCategory {
498+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
499+
(0, Self::EXP_MASK) => FpCategory::Infinite,
500+
(_, Self::EXP_MASK) => FpCategory::Nan,
501+
(0, 0) => FpCategory::Zero,
502+
(_, 0) => FpCategory::Subnormal,
503+
_ => FpCategory::Normal,
504+
}
505+
}
506+
347507
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
348508
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
349509
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.