[libc-commits] [libc] [libc][NFC] Make `EXP_MANT_MASK` an implementation detail (PR #75621)
Guillaume Chatelet via libc-commits
libc-commits at lists.llvm.org
Mon Dec 18 02:45:26 PST 2023
https://github.com/gchatelet updated https://github.com/llvm/llvm-project/pull/75621
>From 2f65694086912908c6ac5f76cf2dccc2fcc06014 Mon Sep 17 00:00:00 2001
From: Guillaume Chatelet <gchatelet at google.com>
Date: Fri, 15 Dec 2023 16:54:35 +0000
Subject: [PATCH 1/3] [libc][NFC] Make EXP_MANT_MASK an implementation detail
This mask is an implementation detail of FPBits and shouldn't really leak outside of it.
I also used this opportunity to document `FloatProperties`.
For now `FloatProperties` is stateless and works on `StorageType` directly whereas `FPBits` is stateful and allows back and forth between FP type and `StorageType`.
---
libc/src/__support/FPUtil/FPBits.h | 12 +-
libc/src/__support/FPUtil/FloatProperties.h | 176 +++++++++++++-----
.../__support/FPUtil/x86_64/LongDoubleBits.h | 1 -
libc/src/math/generic/acoshf.cpp | 3 +-
libc/src/math/generic/asinhf.cpp | 3 +-
libc/src/math/generic/atanhf.cpp | 5 +-
libc/src/math/generic/exp.cpp | 2 +-
libc/src/math/generic/exp10.cpp | 2 +-
libc/src/math/generic/exp2.cpp | 2 +-
libc/src/math/generic/expm1.cpp | 2 +-
libc/src/math/generic/inv_trigf_utils.h | 3 +-
libc/src/math/generic/powf.cpp | 4 +-
libc/src/math/generic/sinhf.cpp | 3 +-
libc/src/math/generic/tanhf.cpp | 3 +-
14 files changed, 152 insertions(+), 69 deletions(-)
diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h
index 7f5dd0fca58d4f..86d80a9af91221 100644
--- a/libc/src/__support/FPUtil/FPBits.h
+++ b/libc/src/__support/FPUtil/FPBits.h
@@ -33,7 +33,11 @@ template <typename T> struct FPBits : private FloatProperties<T> {
"FPBits instantiated with invalid type.");
using typename FloatProperties<T>::StorageType;
using FloatProperties<T>::TOTAL_LEN;
- using FloatProperties<T>::EXP_MANT_MASK;
+
+private:
+ using FloatProperties<T>::EXP_SIG_MASK;
+
+public:
using FloatProperties<T>::EXP_MASK;
using FloatProperties<T>::EXP_BIAS;
using FloatProperties<T>::EXP_LEN;
@@ -146,15 +150,15 @@ template <typename T> struct FPBits : private FloatProperties<T> {
}
LIBC_INLINE constexpr bool is_inf() const {
- return (bits & EXP_MANT_MASK) == EXP_MASK;
+ return (bits & EXP_SIG_MASK) == EXP_MASK;
}
LIBC_INLINE constexpr bool is_nan() const {
- return (bits & EXP_MANT_MASK) > EXP_MASK;
+ return (bits & EXP_SIG_MASK) > EXP_MASK;
}
LIBC_INLINE constexpr bool is_quiet_nan() const {
- return (bits & EXP_MANT_MASK) == (EXP_MASK | QUIET_NAN_MASK);
+ return (bits & EXP_SIG_MASK) == (EXP_MASK | QUIET_NAN_MASK);
}
LIBC_INLINE constexpr bool is_inf_or_nan() const {
diff --git a/libc/src/__support/FPUtil/FloatProperties.h b/libc/src/__support/FPUtil/FloatProperties.h
index 896c29919e2f77..ea1eb5b8b5bbbc 100644
--- a/libc/src/__support/FPUtil/FloatProperties.h
+++ b/libc/src/__support/FPUtil/FloatProperties.h
@@ -28,6 +28,36 @@ enum class FPType {
X86_Binary80,
};
+// Returns the 'FPType' associated with this 'FP' type.
+template <typename FP> LIBC_INLINE static constexpr FPType get_fp_type() {
+ if constexpr (cpp::is_same_v<FP, float> && __FLT_MANT_DIG__ == 24)
+ return FPType::IEEE754_Binary32;
+ else if constexpr (cpp::is_same_v<FP, double> && __DBL_MANT_DIG__ == 53)
+ return FPType::IEEE754_Binary64;
+ else if constexpr (cpp::is_same_v<FP, long double>) {
+ if constexpr (__LDBL_MANT_DIG__ == 53)
+ return FPType::IEEE754_Binary64;
+ else if constexpr (__LDBL_MANT_DIG__ == 64)
+ return FPType::X86_Binary80;
+ else if constexpr (__LDBL_MANT_DIG__ == 113)
+ return FPType::IEEE754_Binary128;
+ }
+#if defined(LIBC_COMPILER_HAS_C23_FLOAT16)
+ else if constexpr (cpp::is_same_v<FP, _Float16>)
+ return FPType::IEEE754_Binary16;
+#endif
+#if defined(LIBC_COMPILER_HAS_C23_FLOAT128)
+ else if constexpr (cpp::is_same_v<FP, _Float128>)
+ return FPType::IEEE754_Binary128;
+#endif
+#if defined(LIBC_COMPILER_HAS_FLOAT128_EXTENSION)
+ else if constexpr (cpp::is_same_v<FP, __float128>)
+ return FPType::IEEE754_Binary128;
+#endif
+ else
+ static_assert(cpp::always_false<FP>, "Unsupported type");
+}
+
// For now 'FPEncoding', 'FPBaseProperties' and 'FPCommonProperties' are
// implementation details.
namespace internal {
@@ -89,29 +119,56 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
using UP = internal::FPBaseProperties<fp_type>;
public:
- // The number of bits to represent sign. For documentation purpose, always 1.
- LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
- using UP::EXP_LEN; // The number of bits for the *exponent* part
- using UP::SIG_LEN; // The number of bits for the *significand* part
- using UP::TOTAL_LEN; // For convenience, the sum of `SIG_LEN`, `EXP_LEN`,
- // and `SIGN_LEN`.
- static_assert(SIGN_LEN + EXP_LEN + SIG_LEN == TOTAL_LEN);
-
+ //---------------------------------------------------------------------------
+ // Physical layer
+ //
+ // At this level, we only describe the bit *ranges* not necessarily their
+ // meaning. A *floating point number* is composed of three parts : a `sign`,
+ // an `exponent` and a `significand`. By convention, they are also designated
+ // by a single letter : `s`, `e` and `m`. Note that `m` here stands for
+ // *mantissa* but its use is generally discouraged :
+ // https://en.wikipedia.org/wiki/Significand#Terminology
+ //
+ // For virtually all of the floating point number formats the bit ranges are
+ // in this exact order : `sign`, `exponent` and `significand`.
+ //
+ // e.g. Depiction of 'IEEE754 Float16'
+ //
+ // sign exponent significand
+ // | ┌───────┐ ┌─────────────────┐
+ // 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0
+ // 15 14 10 9 0
+ //
+ //---------------------------------------------------------------------------
+ // Types
+ //---------------------------------------------------------------------------
// An unsigned integer that is wide enough to contain all of the floating
// point bits.
using StorageType = typename UP::StorageType;
+ //---------------------------------------------------------------------------
+ // Properties
+ //---------------------------------------------------------------------------
+
// The number of bits in StorageType.
LIBC_INLINE_VAR static constexpr int STORAGE_LEN =
sizeof(StorageType) * CHAR_BIT;
- static_assert(STORAGE_LEN >= TOTAL_LEN);
- // The exponent bias. Always positive.
- LIBC_INLINE_VAR static constexpr int32_t EXP_BIAS =
- (1U << (EXP_LEN - 1U)) - 1U;
- static_assert(EXP_BIAS > 0);
+ // The number of bits to represent sign. For documentation purpose, always 1.
+ LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
+ using UP::EXP_LEN; // The number of bits for the *exponent* part
+ using UP::SIG_LEN; // The number of bits for the *significand* part
+ using UP::TOTAL_LEN; // For convenience, the sum of `SIG_LEN`, `EXP_LEN`,
+ // and `SIGN_LEN`.
+ static_assert(SIGN_LEN + EXP_LEN + SIG_LEN == TOTAL_LEN);
+ static_assert(STORAGE_LEN >= TOTAL_LEN);
private:
+ // Helper to set a single bit at 'position'.
+ LIBC_INLINE static constexpr StorageType bit_at(int position) {
+ return StorageType(1) << position;
+ }
+
// The shift amount to get the *significand* part to the least significant
// bit. Always `0` but kept for consistency.
LIBC_INLINE_VAR static constexpr int SIG_MASK_SHIFT = 0;
@@ -120,17 +177,19 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
// The shift amount to get the *sign* part to the least significant bit.
LIBC_INLINE_VAR static constexpr int SIGN_MASK_SHIFT = SIG_LEN + EXP_LEN;
+public:
// The bit pattern that keeps only the *significand* part.
LIBC_INLINE_VAR static constexpr StorageType SIG_MASK =
mask_trailing_ones<StorageType, SIG_LEN>() << SIG_MASK_SHIFT;
-
-public:
// The bit pattern that keeps only the *exponent* part.
LIBC_INLINE_VAR static constexpr StorageType EXP_MASK =
mask_trailing_ones<StorageType, EXP_LEN>() << EXP_MASK_SHIFT;
// The bit pattern that keeps only the *sign* part.
LIBC_INLINE_VAR static constexpr StorageType SIGN_MASK =
mask_trailing_ones<StorageType, SIGN_LEN>() << SIGN_MASK_SHIFT;
+ // The bit pattern that keeps only the *exponent + significand* part.
+ LIBC_INLINE_VAR static constexpr StorageType EXP_SIG_MASK =
+ mask_trailing_ones<StorageType, EXP_LEN + SIG_LEN>();
// The bit pattern that keeps only the *sign + exponent + significand* part.
LIBC_INLINE_VAR static constexpr StorageType FP_MASK =
mask_trailing_ones<StorageType, TOTAL_LEN>();
@@ -138,11 +197,43 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
static_assert((SIG_MASK & EXP_MASK & SIGN_MASK) == 0, "masks disjoint");
static_assert((SIG_MASK | EXP_MASK | SIGN_MASK) == FP_MASK, "masks cover");
-private:
- LIBC_INLINE static constexpr StorageType bit_at(int position) {
- return StorageType(1) << position;
- }
+ // The exponent bias. Always positive.
+ LIBC_INLINE_VAR static constexpr int32_t EXP_BIAS =
+ (1U << (EXP_LEN - 1U)) - 1U;
+ static_assert(EXP_BIAS > 0);
+ //---------------------------------------------------------------------------
+ // Semantic layer
+ //
+ // At this level, we define properties and methods of the floating point
+ // number and regardless of how it's encoded. In this layer, the signficand is
+ // always returned in its normal form and the exponent is always a signed
+ // integer.
+ //
+ // * Normal form : A significand is in a *normalized form* when the leading
+ // `1` is at the position of the most significant bit.
+ //
+ // e.g., Here is the `1.01` normalized significand stored in an `uint16_t`.
+ // ┌──────────────────────────────┐
+ // 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 15 0
+ //
+ // It is always possible to normalize a (non-zero) significand by decreasing
+ // its associated exponent. For instance, the value `0.000000000000001p0` can
+ // be rewritten `1p-15`.
+ //
+ // * Significant working format : By definition `StorageType` can store all of
+ // the bits for the floating point format (`sign` + `exponent` +
+ // `significand`). In the Semantic layer we also use the `StorageType` to
+ // manipulate the `significand`, this means that the number of bits for the
+ // fraction part is greater in the Semantic layer than in the underlying
+ // floating point format.
+ //
+ //
+ //---------------------------------------------------------------------------
+ // Properties
+ //---------------------------------------------------------------------------
+private:
LIBC_INLINE_VAR static constexpr StorageType QNAN_MASK =
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 2) // 0b1100...
@@ -152,7 +243,6 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 3) // 0b1010...
: bit_at(SIG_LEN - 2); // 0b0100...
-
public:
// The number of bits after the decimal dot when the number is in normal form.
LIBC_INLINE_VAR static constexpr int FRACTION_LEN =
@@ -162,44 +252,28 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
FRACTION_LEN + 1;
LIBC_INLINE_VAR static constexpr StorageType FRACTION_MASK =
mask_trailing_ones<StorageType, FRACTION_LEN>();
- LIBC_INLINE_VAR static constexpr StorageType EXP_MANT_MASK =
- EXP_MASK | SIG_MASK;
// If a number x is a NAN, then it is a quiet NAN if:
// QuietNaNMask & bits(x) != 0
// Else, it is a signalling NAN.
static constexpr StorageType QUIET_NAN_MASK = QNAN_MASK;
-};
-//-----------------------------------------------------------------------------
-template <typename FP> LIBC_INLINE static constexpr FPType get_fp_type() {
- if constexpr (cpp::is_same_v<FP, float> && __FLT_MANT_DIG__ == 24)
- return FPType::IEEE754_Binary32;
- else if constexpr (cpp::is_same_v<FP, double> && __DBL_MANT_DIG__ == 53)
- return FPType::IEEE754_Binary64;
- else if constexpr (cpp::is_same_v<FP, long double>) {
- if constexpr (__LDBL_MANT_DIG__ == 53)
- return FPType::IEEE754_Binary64;
- else if constexpr (__LDBL_MANT_DIG__ == 64)
- return FPType::X86_Binary80;
- else if constexpr (__LDBL_MANT_DIG__ == 113)
- return FPType::IEEE754_Binary128;
+ //---------------------------------------------------------------------------
+ // Observers
+ //---------------------------------------------------------------------------
+
+ LIBC_INLINE static constexpr bool sign(StorageType value) {
+ return value & SIGN_MASK;
}
-#if defined(LIBC_COMPILER_HAS_C23_FLOAT16)
- else if constexpr (cpp::is_same_v<FP, _Float16>)
- return FPType::IEEE754_Binary16;
-#endif
-#if defined(LIBC_COMPILER_HAS_C23_FLOAT128)
- else if constexpr (cpp::is_same_v<FP, _Float128>)
- return FPType::IEEE754_Binary128;
-#endif
-#if defined(LIBC_COMPILER_HAS_FLOAT128_EXTENSION)
- else if constexpr (cpp::is_same_v<FP, __float128>)
- return FPType::IEEE754_Binary128;
-#endif
- else
- static_assert(cpp::always_false<FP>, "Unsupported type");
-}
+
+ //---------------------------------------------------------------------------
+ // Modifiers
+ //---------------------------------------------------------------------------
+
+ LIBC_INLINE static constexpr StorageType abs(StorageType value) {
+ return value & EXP_SIG_MASK;
+ }
+};
template <typename FP>
struct FloatProperties : public FPProperties<get_fp_type<FP>()> {};
diff --git a/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h b/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h
index 89c47063ebac4d..b6054b4d61e937 100644
--- a/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h
+++ b/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h
@@ -29,7 +29,6 @@ namespace fputil {
template <> struct FPBits<long double> : private FloatProperties<long double> {
using typename FloatProperties<long double>::StorageType;
using FloatProperties<long double>::TOTAL_LEN;
- using FloatProperties<long double>::EXP_MANT_MASK;
using FloatProperties<long double>::EXP_MASK;
using FloatProperties<long double>::EXP_BIAS;
using FloatProperties<long double>::EXP_LEN;
diff --git a/libc/src/math/generic/acoshf.cpp b/libc/src/math/generic/acoshf.cpp
index 142c17795d083a..708653a5c5f7ce 100644
--- a/libc/src/math/generic/acoshf.cpp
+++ b/libc/src/math/generic/acoshf.cpp
@@ -20,6 +20,7 @@ namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(float, acoshf, (float x)) {
using FPBits_t = typename fputil::FPBits<float>;
+ using FloatProp = typename fputil::FloatProperties<float>;
FPBits_t xbits(x);
uint32_t x_u = xbits.uintval();
@@ -34,7 +35,7 @@ LLVM_LIBC_FUNCTION(float, acoshf, (float x)) {
if (LIBC_UNLIKELY(x_u >= 0x4f8ffb03)) {
// Check for exceptional values.
- uint32_t x_abs = x_u & FPBits_t::EXP_MANT_MASK;
+ uint32_t x_abs = FloatProp::abs(x_u);
if (LIBC_UNLIKELY(x_abs >= 0x7f80'0000U)) {
// x is +inf or NaN.
return x;
diff --git a/libc/src/math/generic/asinhf.cpp b/libc/src/math/generic/asinhf.cpp
index 5b2f63d3fe144e..07adfadc280791 100644
--- a/libc/src/math/generic/asinhf.cpp
+++ b/libc/src/math/generic/asinhf.cpp
@@ -19,9 +19,10 @@ namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(float, asinhf, (float x)) {
using FPBits_t = typename fputil::FPBits<float>;
+ using FloatProp = typename fputil::FloatProperties<float>;
FPBits_t xbits(x);
uint32_t x_u = xbits.uintval();
- uint32_t x_abs = x_u & FPBits_t::EXP_MANT_MASK;
+ uint32_t x_abs = FloatProp::abs(x_u);
// |x| <= 2^-3
if (LIBC_UNLIKELY(x_abs <= 0x3e80'0000U)) {
diff --git a/libc/src/math/generic/atanhf.cpp b/libc/src/math/generic/atanhf.cpp
index dfec28e9a44a72..ac609d0d1c8d71 100644
--- a/libc/src/math/generic/atanhf.cpp
+++ b/libc/src/math/generic/atanhf.cpp
@@ -15,9 +15,10 @@ namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(float, atanhf, (float x)) {
using FPBits = typename fputil::FPBits<float>;
+ using FloatProp = typename fputil::FloatProperties<float>;
FPBits xbits(x);
- bool sign = xbits.get_sign();
- uint32_t x_abs = xbits.uintval() & FPBits::EXP_MANT_MASK;
+ bool sign = FloatProp::sign(xbits.uintval());
+ uint32_t x_abs = FloatProp::abs(xbits.uintval());
// |x| >= 1.0
if (LIBC_UNLIKELY(x_abs >= 0x3F80'0000U)) {
diff --git a/libc/src/math/generic/exp.cpp b/libc/src/math/generic/exp.cpp
index ebfd14dd6cc166..badef28f6c0278 100644
--- a/libc/src/math/generic/exp.cpp
+++ b/libc/src/math/generic/exp.cpp
@@ -178,7 +178,7 @@ double set_exceptional(double x) {
FPBits xbits(x);
uint64_t x_u = xbits.uintval();
- uint64_t x_abs = x_u & FloatProp::EXP_MANT_MASK;
+ uint64_t x_abs = FloatProp::abs(x_u);
// |x| <= 2^-53
if (x_abs <= 0x3ca0'0000'0000'0000ULL) {
diff --git a/libc/src/math/generic/exp10.cpp b/libc/src/math/generic/exp10.cpp
index 4e1babcee541bd..07df55d1ad7145 100644
--- a/libc/src/math/generic/exp10.cpp
+++ b/libc/src/math/generic/exp10.cpp
@@ -225,7 +225,7 @@ double set_exceptional(double x) {
FPBits xbits(x);
uint64_t x_u = xbits.uintval();
- uint64_t x_abs = x_u & FloatProp::EXP_MANT_MASK;
+ uint64_t x_abs = FloatProp::abs( x_u);
// |x| < log10(1 + 2^-53)
if (x_abs <= 0x3c8bcb7b1526e50e) {
diff --git a/libc/src/math/generic/exp2.cpp b/libc/src/math/generic/exp2.cpp
index 07691ca0e7b62a..7574f1777d27c6 100644
--- a/libc/src/math/generic/exp2.cpp
+++ b/libc/src/math/generic/exp2.cpp
@@ -200,7 +200,7 @@ double set_exceptional(double x) {
FPBits xbits(x);
uint64_t x_u = xbits.uintval();
- uint64_t x_abs = x_u & FloatProp::EXP_MANT_MASK;
+ uint64_t x_abs = FloatProp::abs(x_u);
// |x| < log2(1 + 2^-53)
if (x_abs <= 0x3ca71547652b82fd) {
diff --git a/libc/src/math/generic/expm1.cpp b/libc/src/math/generic/expm1.cpp
index a0d47f00828ce4..3114eeb056137e 100644
--- a/libc/src/math/generic/expm1.cpp
+++ b/libc/src/math/generic/expm1.cpp
@@ -223,7 +223,7 @@ double set_exceptional(double x) {
FPBits xbits(x);
uint64_t x_u = xbits.uintval();
- uint64_t x_abs = x_u & FloatProp::EXP_MANT_MASK;
+ uint64_t x_abs = FloatProp::abs( x_u);
// |x| <= 2^-53.
if (x_abs <= 0x3ca0'0000'0000'0000ULL) {
diff --git a/libc/src/math/generic/inv_trigf_utils.h b/libc/src/math/generic/inv_trigf_utils.h
index 588ebbfa71aeb0..600b3a3ccd8924 100644
--- a/libc/src/math/generic/inv_trigf_utils.h
+++ b/libc/src/math/generic/inv_trigf_utils.h
@@ -38,6 +38,7 @@ extern const double ATAN_K[5];
// x should be positive, normal finite value
LIBC_INLINE double atan_eval(double x) {
using FPB = fputil::FPBits<double>;
+ using FloatProp = fputil::FloatProperties<double>;
// Added some small value to umin and umax mantissa to avoid possible rounding
// errors.
FPB::StorageType umin =
@@ -50,7 +51,7 @@ LIBC_INLINE double atan_eval(double x) {
FPB bs(x);
bool sign = bs.get_sign();
- auto x_abs = bs.uintval() & FPB::EXP_MANT_MASK;
+ auto x_abs = FloatProp::abs(bs.uintval());
if (x_abs <= umin) {
double pe = LIBC_NAMESPACE::fputil::polyeval(
diff --git a/libc/src/math/generic/powf.cpp b/libc/src/math/generic/powf.cpp
index dd7fa7f6115d4e..a4d29a2c31dcd4 100644
--- a/libc/src/math/generic/powf.cpp
+++ b/libc/src/math/generic/powf.cpp
@@ -517,9 +517,9 @@ LLVM_LIBC_FUNCTION(float, powf, (float x, float y)) {
FloatBits xbits(x), ybits(y);
uint32_t x_u = xbits.uintval();
- uint32_t x_abs = x_u & FloatProp::EXP_MANT_MASK;
+ uint32_t x_abs = FloatProp::abs(x_u);
uint32_t y_u = ybits.uintval();
- uint32_t y_abs = y_u & FloatProp::EXP_MANT_MASK;
+ uint32_t y_abs = FloatProp::abs(y_u);
///////// BEGIN - Check exceptional cases ////////////////////////////////////
diff --git a/libc/src/math/generic/sinhf.cpp b/libc/src/math/generic/sinhf.cpp
index db6794620b068c..fc5e760f269faa 100644
--- a/libc/src/math/generic/sinhf.cpp
+++ b/libc/src/math/generic/sinhf.cpp
@@ -16,8 +16,9 @@ namespace LIBC_NAMESPACE {
LLVM_LIBC_FUNCTION(float, sinhf, (float x)) {
using FPBits = typename fputil::FPBits<float>;
+ using FloatProp = typename fputil::FloatProperties<float>;
FPBits xbits(x);
- uint32_t x_abs = xbits.uintval() & FPBits::EXP_MANT_MASK;
+ uint32_t x_abs = FloatProp::abs(xbits.uintval());
// When |x| >= 90, or x is inf or nan
if (LIBC_UNLIKELY(x_abs >= 0x42b4'0000U || x_abs <= 0x3da0'0000U)) {
diff --git a/libc/src/math/generic/tanhf.cpp b/libc/src/math/generic/tanhf.cpp
index 9042a41c5ed3fe..71ed6097b039db 100644
--- a/libc/src/math/generic/tanhf.cpp
+++ b/libc/src/math/generic/tanhf.cpp
@@ -22,9 +22,10 @@ constexpr double LOG2_E_EXP2_6 = ExpBase::LOG2_B * 2.0;
LLVM_LIBC_FUNCTION(float, tanhf, (float x)) {
using FPBits = typename fputil::FPBits<float>;
+ using FloatProp = typename fputil::FloatProperties<float>;
FPBits xbits(x);
uint32_t x_u = xbits.uintval();
- uint32_t x_abs = x_u & FPBits::EXP_MANT_MASK;
+ uint32_t x_abs = FloatProp::abs(x_u);
// When |x| >= 15, or x is inf or nan, or |x| <= 0.078125
if (LIBC_UNLIKELY((x_abs >= 0x4170'0000U) || (x_abs <= 0x3da0'0000U))) {
>From c2bf7ae2ea8719a80cf38d815597063d7e57210c Mon Sep 17 00:00:00 2001
From: Guillaume Chatelet <gchatelet at google.com>
Date: Fri, 15 Dec 2023 17:32:47 +0000
Subject: [PATCH 2/3] fix formmatting
---
libc/src/math/generic/exp10.cpp | 2 +-
libc/src/math/generic/expm1.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/libc/src/math/generic/exp10.cpp b/libc/src/math/generic/exp10.cpp
index 07df55d1ad7145..3c6e29b37448a9 100644
--- a/libc/src/math/generic/exp10.cpp
+++ b/libc/src/math/generic/exp10.cpp
@@ -225,7 +225,7 @@ double set_exceptional(double x) {
FPBits xbits(x);
uint64_t x_u = xbits.uintval();
- uint64_t x_abs = FloatProp::abs( x_u);
+ uint64_t x_abs = FloatProp::abs(x_u);
// |x| < log10(1 + 2^-53)
if (x_abs <= 0x3c8bcb7b1526e50e) {
diff --git a/libc/src/math/generic/expm1.cpp b/libc/src/math/generic/expm1.cpp
index 3114eeb056137e..00ae6743e6b4ab 100644
--- a/libc/src/math/generic/expm1.cpp
+++ b/libc/src/math/generic/expm1.cpp
@@ -223,7 +223,7 @@ double set_exceptional(double x) {
FPBits xbits(x);
uint64_t x_u = xbits.uintval();
- uint64_t x_abs = FloatProp::abs( x_u);
+ uint64_t x_abs = FloatProp::abs(x_u);
// |x| <= 2^-53.
if (x_abs <= 0x3ca0'0000'0000'0000ULL) {
>From 8544a156637a0a410bf7d5537f57415190e75100 Mon Sep 17 00:00:00 2001
From: Guillaume Chatelet <gchatelet at google.com>
Date: Mon, 18 Dec 2023 10:45:10 +0000
Subject: [PATCH 3/3] Update FloatProperties documentation
---
libc/src/__support/FPUtil/FloatProperties.h | 71 ++++++++++++++-------
1 file changed, 49 insertions(+), 22 deletions(-)
diff --git a/libc/src/__support/FPUtil/FloatProperties.h b/libc/src/__support/FPUtil/FloatProperties.h
index ea1eb5b8b5bbbc..5f5e281380e8a0 100644
--- a/libc/src/__support/FPUtil/FloatProperties.h
+++ b/libc/src/__support/FPUtil/FloatProperties.h
@@ -129,15 +129,16 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
// *mantissa* but its use is generally discouraged :
// https://en.wikipedia.org/wiki/Significand#Terminology
//
- // For virtually all of the floating point number formats the bit ranges are
- // in this exact order : `sign`, `exponent` and `significand`.
+ // For virtually all of the floating point number formats (and for the purpose
+ // of this library) the bit ranges are in this exact order : `sign`,
+ // `exponent` and `significand`.
//
// e.g. Depiction of 'IEEE754 Float16'
//
- // sign exponent significand
- // | ┌───────┐ ┌─────────────────┐
- // 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0
- // 15 14 10 9 0
+ // | sign exponent significand
+ // | | ┌───────┐ ┌─────────────────┐
+ // | 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0
+ // bit | 15 14 10 9 0
//
//---------------------------------------------------------------------------
// Types
@@ -206,33 +207,56 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
// Semantic layer
//
// At this level, we define properties and methods of the floating point
- // number and regardless of how it's encoded. In this layer, the signficand is
- // always returned in its normal form and the exponent is always a signed
- // integer.
+ // number - regardless of how it's encoded. We typically find methods to
+ // extract the 'sign', 'exponent' and 'significand' of the number or whether
+ // it represents peculiar values like 'infinity' or 'NaN'.
//
- // * Normal form : A significand is in a *normalized form* when the leading
- // `1` is at the position of the most significant bit.
+ // A central notion when interacting with the physical layer is the concept of
+ // 'normalized number'. A normalized number is the association of an exponent
+ // and a significand in a _normalized form_, that is, when its leading `1` is
+ // at the position of the most significant bit.
//
// e.g., Here is the `1.01` normalized significand stored in an `uint16_t`.
+ // The leading '1' is at the most significant bit position.
+ //
// ┌──────────────────────────────┐
// 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
// 15 0
//
- // It is always possible to normalize a (non-zero) significand by decreasing
- // its associated exponent. For instance, the value `0.000000000000001p0` can
- // be rewritten `1p-15`.
- //
- // * Significant working format : By definition `StorageType` can store all of
- // the bits for the floating point format (`sign` + `exponent` +
- // `significand`). In the Semantic layer we also use the `StorageType` to
- // manipulate the `significand`, this means that the number of bits for the
- // fraction part is greater in the Semantic layer than in the underlying
- // floating point format.
- //
+ // Note that it is always possible to normalize a (non-zero) number by
+ // decreasing its associated exponent. For instance, the value `0.001p0` can
+ // be rewritten `1p-3`. Likewise `100p0` can be rewritten `1p2`. It becomes
+ // obvious that the same number can be represented in a number of ways, hence
+ // the importance of a normalized representation.
//
+ // Because 'significand' and 'exponent' usually need to be processed together
+ // they are paired together in the 'Number' type below. This type
+
+ //---------------------------------------------------------------------------
+ // Type
+ //---------------------------------------------------------------------------
+
+ // This is a working format to represent an unsigned floating point number.
+ // Note that numbers expressed in this format have a greater precision than
+ // the physical format:
+ // - The exponent is always 32-bit, this is always greater than 'EXP_LEN' (at
+ // most 15-bit).
+ // - The significand is 'STORAGE_LEN'-bit, this is greater than 'SIG_LEN' by
+ // 'EXP_LEN' bits for IEEE754 format, and by `EXP_LEN + 1` bits for
+ // X86_ExtendedPrecision.
+ struct Number {
+ StorageType significant = 0;
+ int32_t exponent = 0;
+
+ // Returns this Number in a normalized form, i.e., if non-zero, the leading
+ // one is at the most significant bit position.
+ Number normalize() const;
+ };
+
//---------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------
+
private:
LIBC_INLINE_VAR static constexpr StorageType QNAN_MASK =
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
@@ -243,6 +267,7 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 3) // 0b1010...
: bit_at(SIG_LEN - 2); // 0b0100...
+
public:
// The number of bits after the decimal dot when the number is in normal form.
LIBC_INLINE_VAR static constexpr int FRACTION_LEN =
@@ -262,6 +287,7 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
// Observers
//---------------------------------------------------------------------------
+ // Returns whether the 'value' represents a negative number.
LIBC_INLINE static constexpr bool sign(StorageType value) {
return value & SIGN_MASK;
}
@@ -270,6 +296,7 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
// Modifiers
//---------------------------------------------------------------------------
+ // Returns the absolute value of 'value'.
LIBC_INLINE static constexpr StorageType abs(StorageType value) {
return value & EXP_SIG_MASK;
}
More information about the libc-commits
mailing list