Skip to content

Commit c764619

Browse files
committed
Add classify and related methods for f16 and f128
1 parent 2495953 commit c764619

File tree

4 files changed

+374
-39
lines changed

4 files changed

+374
-39
lines changed

library/core/src/num/f128.rs

+121
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,120 @@ 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+
// Other float types cannot use a bitwise classify because they may suffer a variety
455+
// of errors if the backend chooses to cast to different float types (x87). `f128` cannot
456+
// fit into any other float types so this is not a concern, and we rely on bit patterns.
457+
458+
// SAFETY: POD bitcast, same as in `to_bits`.
459+
let bits = unsafe { mem::transmute::<f128, u128>(self) };
460+
Self::classify_bits(bits)
461+
}
462+
463+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
464+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
465+
/// plus a transmute. We do not live in a just world, but we can make it more so.
466+
#[inline]
467+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
468+
const fn classify_bits(b: u128) -> FpCategory {
469+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
470+
(0, Self::EXP_MASK) => FpCategory::Infinite,
471+
(_, Self::EXP_MASK) => FpCategory::Nan,
472+
(0, 0) => FpCategory::Zero,
473+
(_, 0) => FpCategory::Subnormal,
474+
_ => FpCategory::Normal,
475+
}
476+
}
477+
357478
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
358479
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
359480
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that

library/core/src/num/f16.rs

+159-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,157 @@ 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 for f32/f64 tried to only use bitmask-based checks,
440+
// using `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 `to_bits`.
443+
//
444+
// `f16` casts correctly on most platforms, but there are some known bugs (e.g.
445+
// WASM has rounding and quieting bugs). So keep the implementation consistent with
446+
// `f32`/`f64`.
447+
if self.is_infinite() {
448+
// Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
449+
FpCategory::Infinite
450+
} else if self.is_nan() {
451+
// And it may not be NaN, as it can simply be an "overextended" finite value.
452+
FpCategory::Nan
453+
} else {
454+
// However, std can't simply compare to zero to check for zero, either,
455+
// as correctness requires avoiding equality tests that may be Subnormal == -0.0
456+
// because it may be wrong under "denormals are zero" and "flush to zero" modes.
457+
// Most of std's targets don't use those, but they are used for thumbv7neon.
458+
// So, this does use bitpattern matching for the rest.
459+
460+
// SAFETY: f16 to u16 is fine. Usually.
461+
// If classify has gotten this far, the value is definitely in one of these categories.
462+
unsafe { f16::partial_classify(self) }
463+
}
464+
}
465+
466+
/// This doesn't actually return a right answer for NaN on purpose,
467+
/// seeing as how it cannot correctly discern between a floating point NaN,
468+
/// and some normal floating point numbers truncated from an x87 FPU.
469+
///
470+
/// # Safety
471+
///
472+
/// This requires making sure you call this function for values it answers correctly on,
473+
/// otherwise it returns a wrong answer. This is not important for memory safety per se,
474+
/// but getting floats correct is important for not accidentally leaking const eval
475+
/// runtime-deviating logic which may or may not be acceptable.
476+
#[inline]
477+
#[cfg(not(bootstrap))]
478+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
479+
const unsafe fn partial_classify(self) -> FpCategory {
480+
// SAFETY: The caller is not asking questions for which this will tell lies.
481+
let b = unsafe { mem::transmute::<f16, u16>(self) };
482+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
483+
(0, Self::EXP_MASK) => FpCategory::Infinite,
484+
(0, 0) => FpCategory::Zero,
485+
(_, 0) => FpCategory::Subnormal,
486+
_ => FpCategory::Normal,
487+
}
488+
}
489+
490+
/// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
491+
/// FIXME(jubilee): In a just world, this would be the entire impl for classify,
492+
/// plus a transmute. We do not live in a just world, but we can make it more so.
493+
#[inline]
494+
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
495+
const fn classify_bits(b: u16) -> FpCategory {
496+
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
497+
(0, Self::EXP_MASK) => FpCategory::Infinite,
498+
(_, Self::EXP_MASK) => FpCategory::Nan,
499+
(0, 0) => FpCategory::Zero,
500+
(_, 0) => FpCategory::Subnormal,
501+
_ => FpCategory::Normal,
502+
}
503+
}
504+
347505
/// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
348506
/// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
349507
/// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that

0 commit comments

Comments
 (0)