Skip to content

Commit 92f0b11

Browse files
committed
Add classify and related methods for f16 and f128
1 parent 1def498 commit 92f0b11

File tree

4 files changed

+426
-39
lines changed

4 files changed

+426
-39
lines changed

library/core/src/num/f128.rs

Lines changed: 168 additions & 0 deletions
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,167 @@ 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,
398+
/// [subnormal], or NaN.
399+
///
400+
/// ```
401+
/// #![feature(f128)]
402+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
403+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
404+
///
405+
/// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
406+
/// let max = f128::MAX;
407+
/// let lower_than_min = 1.0e-4960_f128;
408+
/// let zero = 0.0_f128;
409+
///
410+
/// assert!(min.is_normal());
411+
/// assert!(max.is_normal());
412+
///
413+
/// assert!(!zero.is_normal());
414+
/// assert!(!f128::NAN.is_normal());
415+
/// assert!(!f128::INFINITY.is_normal());
416+
/// // Values between `0` and `min` are Subnormal.
417+
/// assert!(!lower_than_min.is_normal());
418+
/// # }
419+
/// ```
420+
///
421+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
422+
#[inline]
423+
#[must_use]
424+
#[cfg(not(bootstrap))]
425+
#[unstable(feature = "f128", issue = "116909")]
426+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
427+
pub const fn is_normal(self) -> bool {
428+
matches!(self.classify(), FpCategory::Normal)
429+
}
430+
431+
/// Returns the floating point category of the number. If only one property
432+
/// is going to be tested, it is generally faster to use the specific
433+
/// predicate instead.
434+
///
435+
/// ```
436+
/// #![feature(f128)]
437+
/// # // FIXME(f16_f128): remove when `eqtf2` is available
438+
/// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
439+
///
440+
/// use std::num::FpCategory;
441+
///
442+
/// let num = 12.4_f128;
443+
/// let inf = f128::INFINITY;
444+
///
445+
/// assert_eq!(num.classify(), FpCategory::Normal);
446+
/// assert_eq!(inf.classify(), FpCategory::Infinite);
447+
/// # }
448+
/// ```
449+
#[inline]
450+
#[cfg(not(bootstrap))]
451+
#[unstable(feature = "f128", issue = "116909")]
452+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
453+
pub const fn classify(self) -> FpCategory {
454+
// A previous implementation tried to only use bitmask-based checks,
455+
// using f128::to_bits to transmute the float to its bit repr and match on that.
456+
// Unfortunately, floating point numbers can be much worse than that.
457+
// This also needs to not result in recursive evaluations of f64::to_bits.
458+
//
459+
// On some processors, in some cases, LLVM will "helpfully" lower floating point ops,
460+
// in spite of a request for them using f128 and f64, to things like x87 operations.
461+
// These have an f64's mantissa, but can have a larger than normal exponent.
462+
// FIXME(jubilee): Using x87 operations is never necessary in order to function
463+
// on x86 processors for Rust-to-Rust calls, so this issue should not happen.
464+
// Code generation should be adjusted to use non-C calling conventions, avoiding this.
465+
//
466+
if self.is_infinite() {
467+
// Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
468+
FpCategory::Infinite
469+
} else if self.is_nan() {
470+
// And it may not be NaN, as it can simply be an "overextended" finite value.
471+
FpCategory::Nan
472+
} else {
473+
// However, std can't simply compare to zero to check for zero, either,
474+
// as correctness requires avoiding equality tests that may be Subnormal == -0.0
475+
// because it may be wrong under "denormals are zero" and "flush to zero" modes.
476+
// Most of std's targets don't use those, but they are used for thumbv7neon.
477+
// So, this does use bitpattern matching for the rest.
478+
479+
// SAFETY: f128 to u128 is fine. Usually.
480+
// If classify has gotten this far, the value is definitely in one of these categories.
481+
unsafe { f128::partial_classify(self) }
482+
}
483+
}
484+
485+
/// This doesn't actually return a right answer for NaN on purpose,
486+
/// seeing as how it cannot correctly discern between a floating point NaN,
487+
/// and some normal floating point numbers truncated from an x87 FPU.
488+
/// FIXME(jubilee): This probably could at least answer things correctly for Infinity,
489+
/// like the f64 version does, but I need to run more checks on how things go on x86.
490+
/// I fear losing mantissa data that would have answered that differently.
491+
///
492+
/// # Safety
493+
/// This requires making sure you call this function for values it answers correctly on,
494+
/// otherwise it returns a wrong answer. This is not important for memory safety per se,
495+
/// but getting floats correct is important for not accidentally leaking const eval
496+
/// runtime-deviating logic which may or may not be acceptable.
497+
#[inline]
498+
#[cfg(not(bootstrap))]
499+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
500+
const unsafe fn partial_classify(self) -> FpCategory {
501+
// SAFETY: The caller is not asking questions for which this will tell lies.
502+
let b = unsafe { mem::transmute::<f128, u128>(self) };
503+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
504+
(0, 0) => FpCategory::Zero,
505+
(_, 0) => FpCategory::Subnormal,
506+
_ => FpCategory::Normal,
507+
}
508+
}
509+
510+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
511+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
512+
/// plus a transmute. We do not live in a just world, but we can make it more so.
513+
#[inline]
514+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
515+
const fn classify_bits(b: u128) -> FpCategory {
516+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
517+
(0, Self::EXP_MASK) => FpCategory::Infinite,
518+
(_, Self::EXP_MASK) => FpCategory::Nan,
519+
(0, 0) => FpCategory::Zero,
520+
(_, 0) => FpCategory::Subnormal,
521+
_ => FpCategory::Normal,
522+
}
523+
}
524+
357525
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
358526
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
359527
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that

library/core/src/num/f16.rs

Lines changed: 164 additions & 1 deletion
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,162 @@ 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,
386+
/// [subnormal], or NaN.
387+
///
388+
/// ```
389+
/// #![feature(f16)]
390+
/// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
391+
///
392+
/// let min = f16::MIN_POSITIVE; // 6.1035e-5
393+
/// let max = f16::MAX;
394+
/// let lower_than_min = 1.0e-7_f16;
395+
/// let zero = 0.0_f16;
396+
///
397+
/// assert!(min.is_normal());
398+
/// assert!(max.is_normal());
399+
///
400+
/// assert!(!zero.is_normal());
401+
/// assert!(!f16::NAN.is_normal());
402+
/// assert!(!f16::INFINITY.is_normal());
403+
/// // Values between `0` and `min` are Subnormal.
404+
/// assert!(!lower_than_min.is_normal());
405+
/// # }
406+
/// ```
407+
/// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
408+
#[inline]
409+
#[must_use]
410+
#[cfg(not(bootstrap))]
411+
#[unstable(feature = "f16", issue = "116909")]
412+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
413+
pub const fn is_normal(self) -> bool {
414+
matches!(self.classify(), FpCategory::Normal)
415+
}
416+
417+
/// Returns the floating point category of the number. If only one property
418+
/// is going to be tested, it is generally faster to use the specific
419+
/// predicate instead.
420+
///
421+
/// ```
422+
/// #![feature(f16)]
423+
/// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
424+
///
425+
/// use std::num::FpCategory;
426+
///
427+
/// let num = 12.4_f16;
428+
/// let inf = f16::INFINITY;
429+
///
430+
/// assert_eq!(num.classify(), FpCategory::Normal);
431+
/// assert_eq!(inf.classify(), FpCategory::Infinite);
432+
/// # }
433+
/// ```
434+
#[inline]
435+
#[cfg(not(bootstrap))]
436+
#[unstable(feature = "f16", issue = "116909")]
437+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
438+
pub const fn classify(self) -> FpCategory {
439+
// A previous implementation tried to only use bitmask-based checks,
440+
// using f16::to_bits to transmute the float to its bit repr and match on that.
441+
// Unfortunately, floating point numbers can be much worse than that.
442+
// This also needs to not result in recursive evaluations of f64::to_bits.
443+
//
444+
// On some processors, in some cases, LLVM will "helpfully" lower floating point ops,
445+
// in spite of a request for them using f16 and f64, to things like x87 operations.
446+
// These have an f64's mantissa, but can have a larger than normal exponent.
447+
// FIXME(jubilee): Using x87 operations is never necessary in order to function
448+
// on x86 processors for Rust-to-Rust calls, so this issue should not happen.
449+
// Code generation should be adjusted to use non-C calling conventions, avoiding this.
450+
//
451+
if self.is_infinite() {
452+
// Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
453+
FpCategory::Infinite
454+
} else if self.is_nan() {
455+
// And it may not be NaN, as it can simply be an "overextended" finite value.
456+
FpCategory::Nan
457+
} else {
458+
// However, std can't simply compare to zero to check for zero, either,
459+
// as correctness requires avoiding equality tests that may be Subnormal == -0.0
460+
// because it may be wrong under "denormals are zero" and "flush to zero" modes.
461+
// Most of std's targets don't use those, but they are used for thumbv7neon.
462+
// So, this does use bitpattern matching for the rest.
463+
464+
// SAFETY: f16 to u32 is fine. Usually.
465+
// If classify has gotten this far, the value is definitely in one of these categories.
466+
unsafe { f16::partial_classify(self) }
467+
}
468+
}
469+
470+
/// This doesn't actually return a right answer for NaN on purpose,
471+
/// seeing as how it cannot correctly discern between a floating point NaN,
472+
/// and some normal floating point numbers truncated from an x87 FPU.
473+
/// FIXME(jubilee): This probably could at least answer things correctly for Infinity,
474+
/// like the f64 version does, but I need to run more checks on how things go on x86.
475+
/// I fear losing mantissa data that would have answered that differently.
476+
///
477+
/// # Safety
478+
/// This requires making sure you call this function for values it answers correctly on,
479+
/// otherwise it returns a wrong answer. This is not important for memory safety per se,
480+
/// but getting floats correct is important for not accidentally leaking const eval
481+
/// runtime-deviating logic which may or may not be acceptable.
482+
#[inline]
483+
#[cfg(not(bootstrap))]
484+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
485+
const unsafe fn partial_classify(self) -> FpCategory {
486+
// SAFETY: The caller is not asking questions for which this will tell lies.
487+
let b = unsafe { mem::transmute::<f16, u16>(self) };
488+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
489+
(0, 0) => FpCategory::Zero,
490+
(_, 0) => FpCategory::Subnormal,
491+
_ => FpCategory::Normal,
492+
}
493+
}
494+
495+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
496+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
497+
/// plus a transmute. We do not live in a just world, but we can make it more so.
498+
#[inline]
499+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
500+
const fn classify_bits(b: u16) -> FpCategory {
501+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
502+
(0, Self::EXP_MASK) => FpCategory::Infinite,
503+
(_, Self::EXP_MASK) => FpCategory::Nan,
504+
(0, 0) => FpCategory::Zero,
505+
(_, 0) => FpCategory::Subnormal,
506+
_ => FpCategory::Normal,
507+
}
508+
}
509+
347510
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
348511
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
349512
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that

0 commit comments

Comments
 (0)