[flang-commits] [flang] [flang] Subnormal arguments to and results from SPACING (PR #108861)

Peter Klausler via flang-commits flang-commits at lists.llvm.org
Mon Sep 16 10:19:56 PDT 2024


https://github.com/klausler created https://github.com/llvm/llvm-project/pull/108861

The standards aren't clear about how IEEE-754 subnormal values interact with the intrinsic function SPACING.  Four compilers interpret the standard such that SPACING(x) will return a value never less than TINY(x); one compiler returns TINY(x) for ABS(x) <= TINY(x) but can return SPACING(x) < TINY(x) for some ABS(x) > TINY(x); one other compiler works similarly, but also oddly returns SPACING(x) < TINY(x) for ABS(x) >= TINY(x)/2.

Follow the most common precedent.

>From 9373f897123f2bfa6ac141b9233624696a123db2 Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Mon, 16 Sep 2024 10:14:23 -0700
Subject: [PATCH] [flang] Subnormal arguments to and results from SPACING

The standards aren't clear about how IEEE-754 subnormal values
interact with the intrinsic function SPACING.  Four compilers
interpret the standard such that SPACING(x) will return a value
never less than TINY(x); one compiler returns TINY(x) for
ABS(x) <= TINY(x) but can return SPACING(x) < TINY(x) for some
ABS(x) > TINY(x); one other compiler works similarly, but also
oddly returns SPACING(x) < TINY(x) for ABS(x) >= TINY(x)/2.

Follow the most common precedent.
---
 flang/lib/Evaluate/real.cpp          |  6 ++++--
 flang/runtime/numeric-templates.h    | 27 ++++++++++++++-------------
 flang/test/Evaluate/fold-spacing.f90 |  4 +++-
 3 files changed, 21 insertions(+), 16 deletions(-)

diff --git a/flang/lib/Evaluate/real.cpp b/flang/lib/Evaluate/real.cpp
index 642490646dcaf3..7ea58297c12e8d 100644
--- a/flang/lib/Evaluate/real.cpp
+++ b/flang/lib/Evaluate/real.cpp
@@ -770,11 +770,13 @@ template <typename W, int P> Real<W, P> Real<W, P>::SPACING() const {
   } else if (IsInfinite()) {
     return NotANumber();
   } else if (IsZero() || IsSubnormal()) {
-    return TINY(); // mandated by standard
+    return TINY(); // standard & 100% portable
   } else {
     Real result;
     result.Normalize(false, Exponent(), Fraction::MASKR(1));
-    return result.IsZero() ? TINY() : result;
+    // Can the result be less than TINY()?  No, with four commonly
+    // used compilers; yes, with two less commonly used ones.
+    return result.IsZero() || result.IsSubnormal() ? TINY() : result;
   }
 }
 
diff --git a/flang/runtime/numeric-templates.h b/flang/runtime/numeric-templates.h
index 1b43498a6bfd12..8cc63859ab7d4f 100644
--- a/flang/runtime/numeric-templates.h
+++ b/flang/runtime/numeric-templates.h
@@ -88,12 +88,16 @@ struct MaxOrMinIdentity<TypeCategory::Real, 16, IS_MAXVAL,
 
 // Minimum finite representable value.
 // For floating-point types, returns minimum positive normalized value.
-template <typename T> struct MinValue {
+template <int PREC, typename T> struct MinValue {
   static RT_API_ATTRS T get() { return std::numeric_limits<T>::min(); }
 };
+template <typename T> struct MinValue<11, T> {
+  // TINY(0._2)
+  static constexpr RT_API_ATTRS T get() { return 0.00006103515625E-04; }
+};
 
 #if HAS_FLOAT128
-template <> struct MinValue<CppTypeFor<TypeCategory::Real, 16>> {
+template <> struct MinValue<113, CppTypeFor<TypeCategory::Real, 16>> {
   using Type = CppTypeFor<TypeCategory::Real, 16>;
   static RT_API_ATTRS Type get() {
     // Create a buffer to store binary representation of __float128 constant.
@@ -167,8 +171,8 @@ template <> struct MAXTy<CppTypeFor<TypeCategory::Real, 16>> {
 };
 #endif
 
-template <typename T> struct MINTy {
-  static constexpr RT_API_ATTRS T compute() { return MinValue<T>::get(); }
+template <int PREC, typename T> struct MINTy {
+  static constexpr RT_API_ATTRS T compute() { return MinValue<PREC, T>::get(); }
 };
 
 template <typename T> struct QNANTy {
@@ -339,23 +343,20 @@ template <int PREC, typename T> inline RT_API_ATTRS T RRSpacing(T x) {
 
 // SPACING (16.9.180)
 template <int PREC, typename T> inline RT_API_ATTRS T Spacing(T x) {
+  T tiny{MINTy<PREC, T>::compute()};
   if (ISNANTy<T>::compute(x)) {
     return x; // NaN -> same NaN
   } else if (ISINFTy<T>::compute(x)) {
     return QNANTy<T>::compute(); // +/-Inf -> NaN
   } else if (x == 0) { // 0 -> TINY(x)
-    // The standard-mandated behavior seems broken, since TINY() can't be
-    // subnormal.
-    if constexpr (PREC == 11) { // REAL(2)
-      return 0.00006103515625E-04; // TINY(0._2)
-    } else {
-      // N.B. TINY(0._3) == TINY(0._4) so this works even if no std::bfloat16_t.
-      return MINTy<T>::compute();
-    }
+    return tiny;
   } else {
     T result{LDEXPTy<T>::compute(
         static_cast<T>(1.0), ILOGBTy<T>::compute(x) + 1 - PREC)}; // 2**(e-p)
-    return result == 0 ? /*TINY(x)*/ MINTy<T>::compute() : result;
+    // All compilers return TINY(x) for |x| <= TINY(x), but differ over whether
+    // SPACING(x) can be < TINY(x) for |x| > TINY(x).  The most common precedent
+    // is to never return a value < TINY(x).
+    return result <= tiny ? tiny : result;
   }
 }
 
diff --git a/flang/test/Evaluate/fold-spacing.f90 b/flang/test/Evaluate/fold-spacing.f90
index 1d7b58081d70fe..cfae6eab8b9b8b 100644
--- a/flang/test/Evaluate/fold-spacing.f90
+++ b/flang/test/Evaluate/fold-spacing.f90
@@ -5,7 +5,9 @@ module m
   logical, parameter :: test_2 = spacing(-3.0) == scale(1.0, -22)
   logical, parameter :: test_3 = spacing(3.0d0) == scale(1.0, -51)
   logical, parameter :: test_4 = spacing(0.) == tiny(0.)
-  logical, parameter :: test_5 = spacing(tiny(0.)) == 1.e-45
+  logical, parameter :: test_5a = spacing(tiny(0.)) == tiny(0.)
+  logical, parameter :: test_5b = spacing(tiny(0.)/2) == tiny(0.)
+  logical, parameter :: test_5c = spacing(tiny(0.)*2) == tiny(0.)
   logical, parameter :: test_6 = spacing(8388608.) == 1.
   logical, parameter :: test_7 = spacing(spacing(tiny(.0))) == tiny(0.)
   logical, parameter :: test_11 = rrspacing(3.0) == scale(0.75, 24)



More information about the flang-commits mailing list