[flang-commits] [flang] [llvm] [Flang] Introduce common::numeric_limits (PR #228108)

Michael Kruse via flang-commits flang-commits at lists.llvm.org
Fri Oct 2 05:41:16 PDT 2026


https://github.com/Meinersbur updated https://github.com/llvm/llvm-project/pull/228108

>From 53a59e478e4e82eec5aff821971c4e7a52548bf5 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Thu, 1 Oct 2026 13:20:05 +0200
Subject: [PATCH 1/3] Introduce common::numeric_limits

---
 .../flang-rt/runtime/numeric-templates.h      |  23 ++--
 flang-rt/lib/quadmath/math-entries.h          |   4 +-
 flang-rt/lib/runtime/command.cpp              |   2 +-
 flang-rt/lib/runtime/complex-powi.cpp         |   4 +-
 flang-rt/lib/runtime/namelist.cpp             |   2 +-
 flang-rt/lib/runtime/numeric.cpp              |   8 +-
 flang-rt/lib/runtime/time-intrinsic.cpp       |   4 +-
 flang-rt/lib/runtime/tools.cpp                |   2 +-
 flang-rt/lib/runtime/transformational.cpp     |   2 +-
 flang-rt/unittests/Runtime/ExternalIOTest.cpp |  11 +-
 flang-rt/unittests/Runtime/Numeric.cpp        |  96 ++++++++-------
 .../unittests/Runtime/Transformational.cpp    |   7 +-
 flang/include/flang/Common/erfc-scaled.h      |   6 +-
 flang/include/flang/Common/uint128.h          | 111 +++++++++++++++---
 flang/lib/Decimal/big-radix-floating-point.h  |   4 +-
 flang/lib/Evaluate/constant.cpp               |   2 +-
 16 files changed, 180 insertions(+), 108 deletions(-)

diff --git a/flang-rt/include/flang-rt/runtime/numeric-templates.h b/flang-rt/include/flang-rt/runtime/numeric-templates.h
index e41c7f079e9215..7733a32c98f590 100644
--- a/flang-rt/include/flang-rt/runtime/numeric-templates.h
+++ b/flang-rt/include/flang-rt/runtime/numeric-templates.h
@@ -35,17 +35,8 @@ template <TypeCategory CAT, int KIND, bool IS_MAXVAL, typename Enable = void>
 struct MaxOrMinIdentity {
   using Type = CppTypeFor<CAT, KIND>;
   static constexpr RT_API_ATTRS Type Value() {
-    return IS_MAXVAL ? std::numeric_limits<Type>::lowest()
-                     : std::numeric_limits<Type>::max();
-  }
-};
-
-// std::numeric_limits<> may not know int128_t
-template <bool IS_MAXVAL>
-struct MaxOrMinIdentity<TypeCategory::Integer, 16, IS_MAXVAL> {
-  using Type = CppTypeFor<TypeCategory::Integer, 16>;
-  static constexpr RT_API_ATTRS Type Value() {
-    return IS_MAXVAL ? Type{1} << 127 : ~Type{0} >> 1;
+    return IS_MAXVAL ? common::numeric_limits<Type>::lowest()
+                     : common::numeric_limits<Type>::max();
   }
 };
 
@@ -90,7 +81,7 @@ struct MaxOrMinIdentity<TypeCategory::Real, 16, IS_MAXVAL,
 // Minimum finite representable value.
 // For floating-point types, returns minimum positive normalized value.
 template <int PREC, typename T> struct MinValue {
-  static RT_API_ATTRS T get() { return std::numeric_limits<T>::min(); }
+  static RT_API_ATTRS T get() { return common::numeric_limits<T>::min(); }
 };
 template <typename T> struct MinValue<11, T> {
   // TINY(0._2)
@@ -160,7 +151,7 @@ template <typename T> struct LDEXPTy {
 
 template <typename T> struct MAXTy {
   static constexpr RT_API_ATTRS T compute() {
-    return std::numeric_limits<T>::max();
+    return common::numeric_limits<T>::max();
   }
 };
 
@@ -178,7 +169,7 @@ template <int PREC, typename T> struct MINTy {
 
 template <typename T> struct QNANTy {
   static constexpr RT_API_ATTRS T compute() {
-    return std::numeric_limits<T>::quiet_NaN();
+    return common::numeric_limits<T>::quiet_NaN();
   }
 };
 
@@ -224,8 +215,8 @@ template <typename T> inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) {
     int expo{ILOGBTy<T>::compute(x) + 1};
     auto ip{static_cast<int>(p - expo)};
     if (ip != p - expo) {
-      ip = p < 0 ? std::numeric_limits<int>::min()
-                 : std::numeric_limits<int>::max();
+      ip = p < 0 ? common::numeric_limits<int>::min()
+                 : common::numeric_limits<int>::max();
     }
     return LDEXPTy<T>::compute(x, ip); // x*2**(p-e)
   }
diff --git a/flang-rt/lib/quadmath/math-entries.h b/flang-rt/lib/quadmath/math-entries.h
index 91ad80c8197f49..75cfa7f2041fe6 100644
--- a/flang-rt/lib/quadmath/math-entries.h
+++ b/flang-rt/lib/quadmath/math-entries.h
@@ -220,9 +220,9 @@ DEFINE_SIMPLE_ALIAS(Yn, ynl)
 
 // Use numeric_limits to produce infinity of the right type.
 #define F128_RT_INFINITY \
-  (std::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::infinity())
+  (common::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::infinity())
 #define F128_RT_QNAN \
-  (std::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::quiet_NaN())
+  (common::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::quiet_NaN())
 #elif HAS_LIBMF128
 // We can use __float128 versions of libm functions.
 // __STDC_WANT_IEC_60559_TYPES_EXT__ needs to be defined
diff --git a/flang-rt/lib/runtime/command.cpp b/flang-rt/lib/runtime/command.cpp
index f0f4e8bf64e997..66e1fc787dfa0f 100644
--- a/flang-rt/lib/runtime/command.cpp
+++ b/flang-rt/lib/runtime/command.cpp
@@ -87,7 +87,7 @@ template <int KIND> struct FitsInIntegerKind {
     if constexpr (KIND >= 8) {
       return true;
     } else {
-      return value <= std::numeric_limits<Fortran::runtime::CppTypeFor<
+      return value <= common::numeric_limits<Fortran::runtime::CppTypeFor<
                           Fortran::common::TypeCategory::Integer, KIND>>::max();
     }
   }
diff --git a/flang-rt/lib/runtime/complex-powi.cpp b/flang-rt/lib/runtime/complex-powi.cpp
index a561d114591cf2..61b6f4b265978f 100644
--- a/flang-rt/lib/runtime/complex-powi.cpp
+++ b/flang-rt/lib/runtime/complex-powi.cpp
@@ -24,10 +24,10 @@ template <typename C, typename I> C tgpowi(C base, I exp) {
   }
 
   bool invertResult{exp < 0};
-  bool isMin{exp == std::numeric_limits<I>::min()};
+  bool isMin{exp == common::numeric_limits<I>::min()};
 
   if (isMin) {
-    exp = std::numeric_limits<I>::max();
+    exp = common::numeric_limits<I>::max();
   }
 
   if (exp < 0) {
diff --git a/flang-rt/lib/runtime/namelist.cpp b/flang-rt/lib/runtime/namelist.cpp
index 231b1705a4a2fd..2f248208b88b59 100644
--- a/flang-rt/lib/runtime/namelist.cpp
+++ b/flang-rt/lib/runtime/namelist.cpp
@@ -138,7 +138,7 @@ static RT_API_ATTRS common::optional<SubscriptValue> GetSubscriptValue(
   bool overflow{false};
   while (ch && *ch >= '0' && *ch <= '9') {
     SubscriptValue was{value.value_or(0)};
-    overflow |= was >= std::numeric_limits<SubscriptValue>::max() / 10;
+    overflow |= was >= common::numeric_limits<SubscriptValue>::max() / 10;
     value = 10 * was + *ch - '0';
     io.HandleRelativePosition(byteCount);
     ch = io.GetCurrentChar(byteCount);
diff --git a/flang-rt/lib/runtime/numeric.cpp b/flang-rt/lib/runtime/numeric.cpp
index 78f148dbc5d8ce..835897680d6f1f 100644
--- a/flang-rt/lib/runtime/numeric.cpp
+++ b/flang-rt/lib/runtime/numeric.cpp
@@ -190,9 +190,9 @@ inline RT_API_ATTRS CppTypeFor<TypeCategory::Integer, 4> SelectedRealKind(
 template <int PREC, typename T>
 inline RT_API_ATTRS T Nearest(T x, bool positive) {
   if (positive) {
-    return std::nextafter(x, std::numeric_limits<T>::infinity());
+    return std::nextafter(x, common::numeric_limits<T>::infinity());
   } else {
-    return std::nextafter(x, -std::numeric_limits<T>::infinity());
+    return std::nextafter(x, -common::numeric_limits<T>::infinity());
   }
 }
 
@@ -202,9 +202,9 @@ RT_API_ATTRS BTy FPowI(BTy base, ETy exp) {
   if (exp == ETy{0})
     return BTy{1};
   bool isNegativePower{exp < ETy{0}};
-  bool isMinPower{exp == std::numeric_limits<ETy>::min()};
+  bool isMinPower{exp == common::numeric_limits<ETy>::min()};
   if (isMinPower) {
-    exp = std::numeric_limits<ETy>::max();
+    exp = common::numeric_limits<ETy>::max();
   } else if (isNegativePower) {
     exp = -exp;
   }
diff --git a/flang-rt/lib/runtime/time-intrinsic.cpp b/flang-rt/lib/runtime/time-intrinsic.cpp
index 89e199a1bc79d7..d78f5990d5ce70 100644
--- a/flang-rt/lib/runtime/time-intrinsic.cpp
+++ b/flang-rt/lib/runtime/time-intrinsic.cpp
@@ -270,7 +270,7 @@ template <int KIND> struct StoreNegativeHugeAt {
       const Fortran::runtime::Descriptor &result, std::size_t at) const {
     *result.ZeroBasedIndexedElement<Fortran::runtime::CppTypeFor<
         Fortran::common::TypeCategory::Integer, KIND>>(at) =
-        -std::numeric_limits<Fortran::runtime::CppTypeFor<
+        -common::numeric_limits<Fortran::runtime::CppTypeFor<
             Fortran::common::TypeCategory::Integer, KIND>>::max();
   }
 };
@@ -383,7 +383,7 @@ GetGmtOffset(const TM &tm, fallback_implementation) {
   // tm.tm_gmtoff is not available, there may be platform dependent alternatives
   // (such as using timezone from <time.h> when available), but so far just
   // return -HUGE to report that this information is not available.
-  const auto negHuge{-std::numeric_limits<Fortran::runtime::CppTypeFor<
+  const auto negHuge{-common::numeric_limits<Fortran::runtime::CppTypeFor<
       Fortran::common::TypeCategory::Integer, KIND>>::max()};
 #if defined _AIX
   bool err{false};
diff --git a/flang-rt/lib/runtime/tools.cpp b/flang-rt/lib/runtime/tools.cpp
index 8ef7106f6a7c57..45a56fd7d71e70 100644
--- a/flang-rt/lib/runtime/tools.cpp
+++ b/flang-rt/lib/runtime/tools.cpp
@@ -330,7 +330,7 @@ template <int KIND> struct FitsInIntegerKind {
       return true;
     } else {
       return value <=
-          std::numeric_limits<
+          common::numeric_limits<
               CppTypeFor<Fortran::common::TypeCategory::Integer, KIND>>::max();
     }
   }
diff --git a/flang-rt/lib/runtime/transformational.cpp b/flang-rt/lib/runtime/transformational.cpp
index 395721ca2b9bd9..4e6f24ed79e8c4 100644
--- a/flang-rt/lib/runtime/transformational.cpp
+++ b/flang-rt/lib/runtime/transformational.cpp
@@ -320,7 +320,7 @@ static inline RT_API_ATTRS void DoBesselYnX0(Descriptor &result, int32_t n1,
   for (int j{1}; j <= n2 - n1 + 1; ++j) {
     at[0] = j;
     *result.Element<CppTypeFor<CAT, KIND>>(at) =
-        -std::numeric_limits<CppTypeFor<CAT, KIND>>::infinity();
+        -common::numeric_limits<CppTypeFor<CAT, KIND>>::infinity();
   }
 }
 
diff --git a/flang-rt/unittests/Runtime/ExternalIOTest.cpp b/flang-rt/unittests/Runtime/ExternalIOTest.cpp
index 1fe3b08126c5d0..cf469e5b34d400 100644
--- a/flang-rt/unittests/Runtime/ExternalIOTest.cpp
+++ b/flang-rt/unittests/Runtime/ExternalIOTest.cpp
@@ -978,12 +978,15 @@ TEST(ExternalIOTests, TestUCS) {
 }
 
 TEST(ExternalIOTests, BigUnitNumbers) {
-  if (std::numeric_limits<ExternalUnit>::max() <
-      std::numeric_limits<std::int64_t>::max()) {
-    std::int64_t unit64Ok = std::numeric_limits<ExternalUnit>::max();
+  if (Fortran::common::numeric_limits<ExternalUnit>::max() <
+      Fortran::common::numeric_limits<std::int64_t>::max()) {
+    std::int64_t unit64Ok =
+        Fortran::common::numeric_limits<ExternalUnit>::max();
     std::int64_t unit64Bad = unit64Ok + 1;
     std::int64_t unit64Bad2 =
-        static_cast<std::int64_t>(std::numeric_limits<ExternalUnit>::min()) - 1;
+        static_cast<std::int64_t>(
+            Fortran::common::numeric_limits<ExternalUnit>::min()) -
+        1;
     EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Ok, true), IostatOk);
     EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Ok, false), IostatOk);
     EXPECT_EQ(
diff --git a/flang-rt/unittests/Runtime/Numeric.cpp b/flang-rt/unittests/Runtime/Numeric.cpp
index 4baad3fc9ad0eb..1c8ccc42626680 100644
--- a/flang-rt/unittests/Runtime/Numeric.cpp
+++ b/flang-rt/unittests/Runtime/Numeric.cpp
@@ -13,6 +13,7 @@
 #include <limits>
 
 using namespace Fortran::runtime;
+using Fortran::common::numeric_limits;
 using Fortran::common::TypeCategory;
 template <int KIND> using Int = CppTypeFor<TypeCategory::Integer, KIND>;
 template <int KIND> using Real = CppTypeFor<TypeCategory::Real, KIND>;
@@ -43,25 +44,25 @@ TEST(Numeric, Exponent) {
   EXPECT_EQ(RTNAME(Exponent4_4)(Real<4>{0}), 0);
   EXPECT_EQ(RTNAME(Exponent4_8)(Real<4>{1.0}), 1);
   EXPECT_EQ(RTNAME(Exponent8_4)(Real<8>{4.1}), 3);
-  EXPECT_EQ(RTNAME(Exponent8_8)(std::numeric_limits<Real<8>>::infinity()),
-      std::numeric_limits<Int<8>>::max());
-  EXPECT_EQ(RTNAME(Exponent8_8)(std::numeric_limits<Real<8>>::quiet_NaN()),
-      std::numeric_limits<Int<8>>::max());
+  EXPECT_EQ(RTNAME(Exponent8_8)(numeric_limits<Real<8>>::infinity()),
+      numeric_limits<Int<8>>::max());
+  EXPECT_EQ(RTNAME(Exponent8_8)(numeric_limits<Real<8>>::quiet_NaN()),
+      numeric_limits<Int<8>>::max());
 }
 
 TEST(Numeric, Fraction) {
   EXPECT_EQ(RTNAME(Fraction4)(Real<4>{0}), 0);
   EXPECT_EQ(RTNAME(Fraction8)(Real<8>{3.0}), 0.75);
   EXPECT_TRUE(
-      std::isnan(RTNAME(Fraction4)(std::numeric_limits<Real<4>>::infinity())));
+      std::isnan(RTNAME(Fraction4)(numeric_limits<Real<4>>::infinity())));
   EXPECT_TRUE(
-      std::isnan(RTNAME(Fraction8)(std::numeric_limits<Real<8>>::quiet_NaN())));
+      std::isnan(RTNAME(Fraction8)(numeric_limits<Real<8>>::quiet_NaN())));
 }
 
 TEST(Numeric, IsNaN) {
   EXPECT_FALSE(RTNAME(IsNaN4)(Real<4>{0}));
-  EXPECT_FALSE(RTNAME(IsNaN8)(std::numeric_limits<Real<8>>::infinity()));
-  EXPECT_TRUE(RTNAME(IsNaN8)(std::numeric_limits<Real<8>>::quiet_NaN()));
+  EXPECT_FALSE(RTNAME(IsNaN8)(numeric_limits<Real<8>>::infinity()));
+  EXPECT_TRUE(RTNAME(IsNaN8)(numeric_limits<Real<8>>::quiet_NaN()));
 }
 
 TEST(Numeric, Mod) {
@@ -74,28 +75,26 @@ TEST(Numeric, Mod) {
   EXPECT_EQ(RTNAME(ModReal8)(Real<8>{8.0}, Real<8>(-5.0)), 3.0);
   EXPECT_EQ(RTNAME(ModReal8)(Real<8>{-8.0}, Real<8>(-5.0)), -3.0);
   EXPECT_EQ(
-      RTNAME(ModReal4)(Real<4>{0.5}, std::numeric_limits<Real<4>>::infinity()),
-      0.5);
+      RTNAME(ModReal4)(Real<4>{0.5}, numeric_limits<Real<4>>::infinity()), 0.5);
   EXPECT_EQ(
-      RTNAME(ModReal4)(Real<4>{-0.5}, std::numeric_limits<Real<4>>::infinity()),
+      RTNAME(ModReal4)(Real<4>{-0.5}, numeric_limits<Real<4>>::infinity()),
       -0.5);
   EXPECT_EQ(
-      RTNAME(ModReal4)(Real<4>{0.5}, -std::numeric_limits<Real<4>>::infinity()),
+      RTNAME(ModReal4)(Real<4>{0.5}, -numeric_limits<Real<4>>::infinity()),
       0.5);
-  EXPECT_EQ(RTNAME(ModReal4)(
-                Real<4>{-0.5}, -std::numeric_limits<Real<4>>::infinity()),
+  EXPECT_EQ(
+      RTNAME(ModReal4)(Real<4>{-0.5}, -numeric_limits<Real<4>>::infinity()),
       -0.5);
   EXPECT_EQ(
-      RTNAME(ModReal8)(Real<8>{0.5}, std::numeric_limits<Real<8>>::infinity()),
-      0.5);
+      RTNAME(ModReal8)(Real<8>{0.5}, numeric_limits<Real<8>>::infinity()), 0.5);
   EXPECT_EQ(
-      RTNAME(ModReal8)(Real<8>{-0.5}, std::numeric_limits<Real<8>>::infinity()),
+      RTNAME(ModReal8)(Real<8>{-0.5}, numeric_limits<Real<8>>::infinity()),
       -0.5);
   EXPECT_EQ(
-      RTNAME(ModReal8)(Real<8>{0.5}, -std::numeric_limits<Real<8>>::infinity()),
+      RTNAME(ModReal8)(Real<8>{0.5}, -numeric_limits<Real<8>>::infinity()),
       0.5);
-  EXPECT_EQ(RTNAME(ModReal8)(
-                Real<8>{-0.5}, -std::numeric_limits<Real<8>>::infinity()),
+  EXPECT_EQ(
+      RTNAME(ModReal8)(Real<8>{-0.5}, -numeric_limits<Real<8>>::infinity()),
       -0.5);
 }
 
@@ -109,22 +108,22 @@ TEST(Numeric, Modulo) {
   EXPECT_EQ(RTNAME(ModuloReal8)(Real<8>{8.0}, Real<8>(-5.0)), -2.0);
   EXPECT_EQ(RTNAME(ModuloReal8)(Real<8>{-8.0}, Real<8>(-5.0)), -3.0);
   // MODULO(x, INF) == NaN
+  EXPECT_TRUE(std::isnan(
+      RTNAME(ModuloReal4)(Real<4>{0.5}, numeric_limits<Real<4>>::infinity())));
+  EXPECT_TRUE(std::isnan(
+      RTNAME(ModuloReal4)(Real<4>{-0.5}, numeric_limits<Real<4>>::infinity())));
+  EXPECT_TRUE(std::isnan(
+      RTNAME(ModuloReal4)(Real<4>{0.5}, -numeric_limits<Real<4>>::infinity())));
   EXPECT_TRUE(std::isnan(RTNAME(ModuloReal4)(
-      Real<4>{0.5}, std::numeric_limits<Real<4>>::infinity())));
-  EXPECT_TRUE(std::isnan(RTNAME(ModuloReal4)(
-      Real<4>{-0.5}, std::numeric_limits<Real<4>>::infinity())));
-  EXPECT_TRUE(std::isnan(RTNAME(ModuloReal4)(
-      Real<4>{0.5}, -std::numeric_limits<Real<4>>::infinity())));
-  EXPECT_TRUE(std::isnan(RTNAME(ModuloReal4)(
-      Real<4>{-0.5}, -std::numeric_limits<Real<4>>::infinity())));
-  EXPECT_TRUE(std::isnan(RTNAME(ModuloReal8)(
-      Real<8>{-0.5}, std::numeric_limits<Real<8>>::infinity())));
-  EXPECT_TRUE(std::isnan(RTNAME(ModuloReal8)(
-      Real<8>{0.5}, std::numeric_limits<Real<8>>::infinity())));
-  EXPECT_TRUE(std::isnan(RTNAME(ModuloReal8)(
-      Real<8>{-0.5}, -std::numeric_limits<Real<8>>::infinity())));
+      Real<4>{-0.5}, -numeric_limits<Real<4>>::infinity())));
+  EXPECT_TRUE(std::isnan(
+      RTNAME(ModuloReal8)(Real<8>{-0.5}, numeric_limits<Real<8>>::infinity())));
+  EXPECT_TRUE(std::isnan(
+      RTNAME(ModuloReal8)(Real<8>{0.5}, numeric_limits<Real<8>>::infinity())));
   EXPECT_TRUE(std::isnan(RTNAME(ModuloReal8)(
-      Real<8>{0.5}, -std::numeric_limits<Real<8>>::infinity())));
+      Real<8>{-0.5}, -numeric_limits<Real<8>>::infinity())));
+  EXPECT_TRUE(std::isnan(
+      RTNAME(ModuloReal8)(Real<8>{0.5}, -numeric_limits<Real<8>>::infinity())));
   // MODULO(x, y) for integer values of x and y with 0 remainder.
   EXPECT_EQ(RTNAME(ModuloReal4)(Real<4>{5.0}, Real<4>(1.0)), 0.0);
   EXPECT_EQ(RTNAME(ModuloReal4)(Real<4>{5.0}, Real<4>(-1.0)), -0.0);
@@ -134,7 +133,7 @@ TEST(Numeric, Modulo) {
 
 TEST(Numeric, Nearest) {
   EXPECT_EQ(RTNAME(Nearest4)(Real<4>{0}, true),
-      std::numeric_limits<Real<4>>::denorm_min());
+      numeric_limits<Real<4>>::denorm_min());
   EXPECT_EQ(RTNAME(Nearest4)(Real<4>{3.0}, true),
       Real<4>{3.0} + std::ldexp(Real<4>{1.0}, -22));
   EXPECT_EQ(RTNAME(Nearest8)(Real<8>{1.0}, true),
@@ -156,9 +155,9 @@ TEST(Numeric, RRSpacing) {
   EXPECT_EQ(RTNAME(RRSpacing4)(Real<4>{-3.0}), 0.75 * (1 << 24));
   EXPECT_EQ(RTNAME(RRSpacing8)(Real<8>{-3.0}), 0.75 * (std::int64_t{1} << 53));
   EXPECT_TRUE(
-      std::isnan(RTNAME(RRSpacing4)(std::numeric_limits<Real<4>>::infinity())));
-  EXPECT_TRUE(std::isnan(
-      RTNAME(RRSpacing8)(std::numeric_limits<Real<8>>::quiet_NaN())));
+      std::isnan(RTNAME(RRSpacing4)(numeric_limits<Real<4>>::infinity())));
+  EXPECT_TRUE(
+      std::isnan(RTNAME(RRSpacing8)(numeric_limits<Real<8>>::quiet_NaN())));
 }
 
 TEST(Numeric, Scale) {
@@ -167,9 +166,9 @@ TEST(Numeric, Scale) {
   EXPECT_EQ(RTNAME(Scale4)(Real<4>{1.0}, 1), 2.0);
   EXPECT_EQ(RTNAME(Scale4)(Real<4>{1.0}, -1), 0.5);
   EXPECT_TRUE(
-      std::isinf(RTNAME(Scale4)(std::numeric_limits<Real<4>>::infinity(), 1)));
+      std::isinf(RTNAME(Scale4)(numeric_limits<Real<4>>::infinity(), 1)));
   EXPECT_TRUE(
-      std::isnan(RTNAME(Scale8)(std::numeric_limits<Real<8>>::quiet_NaN(), 1)));
+      std::isnan(RTNAME(Scale8)(numeric_limits<Real<8>>::quiet_NaN(), 1)));
 }
 
 TEST(Numeric, SetExponent) {
@@ -179,10 +178,10 @@ TEST(Numeric, SetExponent) {
   EXPECT_EQ(RTNAME(SetExponent4)(Real<4>{1.0}, 0), 0.5);
   EXPECT_EQ(RTNAME(SetExponent4)(Real<4>{1.0}, 1), 1.0);
   EXPECT_EQ(RTNAME(SetExponent4)(Real<4>{1.0}, -1), 0.25);
+  EXPECT_TRUE(
+      std::isnan(RTNAME(SetExponent4)(numeric_limits<Real<4>>::infinity(), 1)));
   EXPECT_TRUE(std::isnan(
-      RTNAME(SetExponent4)(std::numeric_limits<Real<4>>::infinity(), 1)));
-  EXPECT_TRUE(std::isnan(
-      RTNAME(SetExponent8)(std::numeric_limits<Real<8>>::quiet_NaN(), 1)));
+      RTNAME(SetExponent8)(numeric_limits<Real<8>>::quiet_NaN(), 1)));
 }
 
 TEST(Numeric, SelectedIntKind) {
@@ -253,17 +252,16 @@ TEST(Numeric, SelectedRealKind) {
 }
 
 TEST(Numeric, Spacing) {
-  EXPECT_EQ(RTNAME(Spacing8)(Real<8>{0}), std::numeric_limits<Real<8>>::min());
+  EXPECT_EQ(RTNAME(Spacing8)(Real<8>{0}), numeric_limits<Real<8>>::min());
   EXPECT_EQ(RTNAME(Spacing4)(Real<4>{3.0}), std::ldexp(Real<4>{1.0}, -22));
   EXPECT_TRUE(
-      std::isnan(RTNAME(Spacing4)(std::numeric_limits<Real<4>>::infinity())));
+      std::isnan(RTNAME(Spacing4)(numeric_limits<Real<4>>::infinity())));
   EXPECT_TRUE(
-      std::isnan(RTNAME(Spacing8)(std::numeric_limits<Real<8>>::quiet_NaN())));
+      std::isnan(RTNAME(Spacing8)(numeric_limits<Real<8>>::quiet_NaN())));
   EXPECT_EQ(RTNAME(Spacing2By4)(Real<4>{3.0}), std::ldexp(Real<4>{1.0}, -9));
   EXPECT_EQ(RTNAME(Spacing2By4)(Real<4>{0.0}), Real<4>{0.00006103515625E-04});
   EXPECT_EQ(RTNAME(Spacing3By4)(Real<4>{3.0}), std::ldexp(Real<4>{1.0}, -6));
-  EXPECT_EQ(
-      RTNAME(Spacing3By4)(Real<4>{0.0}), std::numeric_limits<Real<4>>::min());
+  EXPECT_EQ(RTNAME(Spacing3By4)(Real<4>{0.0}), numeric_limits<Real<4>>::min());
 }
 
 TEST(Numeric, FPowI) {
@@ -334,7 +332,7 @@ TEST(Numeric, FPowI) {
     double result;
     *reinterpret_cast<std::uint64_t *>(&result) = 4607182414505051137ULL;
     EXPECT_TRUE(std::abs(RTNAME(FPow8i)(Real<8>{base},
-                             Int<4>{std::numeric_limits<Int<4>>::min()}) -
+                             Int<4>{numeric_limits<Int<4>>::min()}) -
                     Real<8>{result}) < 0.00000000001);
 
     // (0x3FF0000000000001 ** 4294967296ULL) ~ 0x3FF00001000007FF
diff --git a/flang-rt/unittests/Runtime/Transformational.cpp b/flang-rt/unittests/Runtime/Transformational.cpp
index e61f5a64fcdb08..b883aefc1f85dc 100644
--- a/flang-rt/unittests/Runtime/Transformational.cpp
+++ b/flang-rt/unittests/Runtime/Transformational.cpp
@@ -11,6 +11,7 @@
 #include "gtest/gtest.h"
 #include "flang-rt/runtime/type-code.h"
 #include "flang/Common/float128.h"
+#include "flang/Common/uint128.h"
 #include <vector>
 
 using namespace Fortran::runtime;
@@ -26,8 +27,8 @@ using BesselX0FuncType =
     std::function<void(Descriptor &, int32_t, int32_t, const char *, int)>;
 
 template <int KIND>
-constexpr CppTypeFor<TypeCategory::Real, KIND>
-    besselEpsilon = CppTypeFor<TypeCategory::Real, KIND>(1e-4);
+constexpr CppTypeFor<TypeCategory::Real, KIND> besselEpsilon =
+    CppTypeFor<TypeCategory::Real, KIND>(1e-4);
 
 template <int KIND>
 static void testBesselJn(BesselFuncType<KIND> rtFunc, int32_t n1, int32_t n2,
@@ -176,7 +177,7 @@ static void testBesselYnX0(
     EXPECT_EQ(
         (*result.ZeroBasedIndexedElement<CppTypeFor<TypeCategory::Real, KIND>>(
             j)),
-        (-std::numeric_limits<
+        (-Fortran::common::numeric_limits<
             CppTypeFor<TypeCategory::Real, KIND>>::infinity()));
   }
 }
diff --git a/flang/include/flang/Common/erfc-scaled.h b/flang/include/flang/Common/erfc-scaled.h
index 001af66baf34a4..9be61de162f180 100644
--- a/flang/include/flang/Common/erfc-scaled.h
+++ b/flang/include/flang/Common/erfc-scaled.h
@@ -39,7 +39,7 @@ template <typename T> inline RT_API_ATTRS T ErfcScaled(T arg) {
 
   constexpr T sqrtpi{1.7724538509078120380404576221783883301349L};
   constexpr T rsqrtpi{0.5641895835477562869480794515607725858440L};
-  constexpr T epsilonby2{std::numeric_limits<T>::epsilon() * 0.5};
+  constexpr T epsilonby2{common::numeric_limits<T>::epsilon() * 0.5};
   constexpr T xneg{-26.628e0};
   constexpr T xhuge{6.71e7};
   constexpr T thresh{0.46875e0};
@@ -47,7 +47,7 @@ template <typename T> inline RT_API_ATTRS T ErfcScaled(T arg) {
   constexpr T one{1.0};
   constexpr T four{4.0};
   constexpr T sixteen{16.0};
-  constexpr T xmax{1.0 / (sqrtpi * std::numeric_limits<T>::min())};
+  constexpr T xmax{1.0 / (sqrtpi * common::numeric_limits<T>::min())};
   static_assert(xmax > xhuge, "xmax must be greater than xhuge");
 
   T ysq;
@@ -106,7 +106,7 @@ template <typename T> inline RT_API_ATTRS T ErfcScaled(T arg) {
   //  fix up for negative argument, erf, etc.
   if (x < zero) {
     if (x < xneg) {
-      result = std::numeric_limits<T>::max();
+      result = common::numeric_limits<T>::max();
     } else {
       ysq = trunc(x * sixteen) / sixteen;
       del = (x - ysq) * (x + ysq);
diff --git a/flang/include/flang/Common/uint128.h b/flang/include/flang/Common/uint128.h
index c4bc4689a1eaae..4903ddc56558e7 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -22,30 +22,39 @@
 #include "api-attrs.h"
 #include "leading-zero-bit-count.h"
 #include <cstdint>
+#include <limits>
 #include <type_traits>
 
 namespace Fortran::common {
+namespace detail {
+template <typename T> class numeric_limits;
+}
+
+/// Same as std::numeric_limits, but also defined for
+/// UnsignedInt128/SignedInt128.
+/// While std::numeric_limits is allowed to be extended for user-defined types
+/// (C++ [namespace.std]), it is not for the MSVC STL __int128/__uint128
+/// workaround below.
+template <typename T>
+using numeric_limits = std::conditional_t<std::is_arithmetic_v<T>,
+    std::numeric_limits<T>, detail::numeric_limits<T>>;
 
 template <bool IS_SIGNED = false> class Int128 {
+  friend class detail::numeric_limits<Int128>;
+
 public:
   constexpr Int128() {}
   // This means of definition provides some portability for
   // "size_t" operands.
-  constexpr Int128(unsigned n) : low_{n} {}
-  constexpr Int128(unsigned long n) : low_{n} {}
-  constexpr Int128(unsigned long long n) : low_{n} {}
-  constexpr Int128(int n) {
+  template <typename T,
+      typename = std::enable_if_t<std::is_integral_v<T> && sizeof(T) <= 8>>
+  constexpr Int128(T n) {
     low_ = static_cast<std::uint64_t>(n);
-    high_ = -static_cast<std::uint64_t>(n < 0);
-  }
-  constexpr Int128(long n) {
-    low_ = static_cast<std::uint64_t>(n);
-    high_ = -static_cast<std::uint64_t>(n < 0);
-  }
-  constexpr Int128(long long n) {
-    low_ = static_cast<std::uint64_t>(n);
-    high_ = -static_cast<std::uint64_t>(n < 0);
+    if constexpr (std::is_signed_v<T>) {
+      high_ = -static_cast<std::uint64_t>(n < 0);
+    }
   }
+
   constexpr Int128(const Int128 &) = default;
   constexpr Int128(Int128 &&) = default;
   constexpr Int128 &operator=(const Int128 &) = default;
@@ -61,9 +70,12 @@ template <bool IS_SIGNED = false> class Int128 {
   constexpr Int128 operator-() const { return ~*this + 1; }
   constexpr bool operator!() const { return !low_ && !high_; }
   constexpr explicit operator bool() const { return low_ || high_; }
-  constexpr explicit operator std::uint64_t() const { return low_; }
-  constexpr explicit operator std::int64_t() const { return low_; }
-  constexpr explicit operator int() const { return static_cast<int>(low_); }
+
+  template <typename T,
+      typename = std::enable_if_t<std::is_integral_v<T> && sizeof(T) <= 8>>
+  constexpr explicit operator T() const {
+    return static_cast<T>(low_);
+  }
 
   constexpr std::uint64_t high() const { return high_; }
   constexpr std::uint64_t low() const { return low_; }
@@ -304,5 +316,72 @@ using HostUnsignedIntType = typename HostUnsignedIntTypeHelper<BITS>::type;
 template <int BITS>
 using HostSignedIntType = typename HostSignedIntTypeHelper<BITS>::type;
 
+namespace detail {
+
+template <> class numeric_limits<Fortran::common::UnsignedInt128> {
+public:
+  using T = Fortran::common::UnsignedInt128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{false};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() { return T{0, 0}; }
+  static constexpr T max() { return T{UINT64_MAX, UINT64_MAX}; }
+  static constexpr T lowest() { return min(); }
+};
+
+template <> class numeric_limits<Fortran::common::SignedInt128> {
+public:
+  using T = Fortran::common::SignedInt128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{true};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() {
+    return T{static_cast<std::uint64_t>(INT64_MIN), 0};
+  }
+  static constexpr T max() {
+    return T{static_cast<std::uint64_t>(INT64_MAX), UINT64_MAX};
+  }
+  static constexpr T lowest() { return min(); }
+};
+
+#if defined(__SIZEOF_INT128__) && defined(_MSVC_STL_VERSION)
+// clang-cl knows __int128 and will be used for (u)int128_t, but the MSVC STL
+// does not define stl::numeric_limits for it.
+
+template <> class numeric_limits<unsigned __int128> {
+public:
+  using T = unsigned __int128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{false};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() { return static_cast<T>(0); }
+  static constexpr T max() { return ~static_cast<T>(0); }
+  static constexpr T lowest() { return min(); }
+};
+
+template <> class numeric_limits<__int128> {
+public:
+  using T = __int128;
+
+  static constexpr bool is_specialized{true};
+  static constexpr bool is_signed{true};
+  static constexpr bool is_integer{true};
+
+  static constexpr T min() {
+    return static_cast<T>(static_cast<unsigned __int128>(1) << 127u);
+  }
+  static constexpr T max() {
+    return static_cast<T>(~(static_cast<unsigned __int128>(1) << 127u));
+  }
+  static constexpr T lowest() { return min(); }
+};
+#endif
+} // namespace detail
 } // namespace Fortran::common
 #endif
diff --git a/flang/lib/Decimal/big-radix-floating-point.h b/flang/lib/Decimal/big-radix-floating-point.h
index 63890f271f6e55..22d38d69676358 100644
--- a/flang/lib/Decimal/big-radix-floating-point.h
+++ b/flang/lib/Decimal/big-radix-floating-point.h
@@ -53,9 +53,9 @@ template <int PREC, int LOG10RADIX = 16> class BigRadixFloatingPointNumber {
       64 - common::LeadingZeroBitCount(uint64Radix)};
   using Digit = common::HostUnsignedIntType<minDigitBits>;
   static constexpr Digit radix{uint64Radix};
-  static_assert(radix < std::numeric_limits<Digit>::max() / 1000,
+  static_assert(radix < common::numeric_limits<Digit>::max() / 1000,
       "radix is somehow too big");
-  static_assert(radix > std::numeric_limits<Digit>::max() / 10000,
+  static_assert(radix > common::numeric_limits<Digit>::max() / 10000,
       "radix is somehow too small");
 
   // The base-2 logarithm of the least significant bit that can arise
diff --git a/flang/lib/Evaluate/constant.cpp b/flang/lib/Evaluate/constant.cpp
index 7fe000892ac1a2..d0552dc4d76da6 100644
--- a/flang/lib/Evaluate/constant.cpp
+++ b/flang/lib/Evaluate/constant.cpp
@@ -87,7 +87,7 @@ std::optional<uint64_t> TotalElementCount(const ConstantSubscripts &shape) {
     CHECK(dim >= 0);
     uint64_t osize{size};
     size = osize * dim;
-    if (size > std::numeric_limits<decltype(dim)>::max() ||
+    if (size > common::numeric_limits<decltype(dim)>::max() ||
         (dim != 0 && size / dim != osize)) {
       return std::nullopt;
     }

>From 16bd62a88d1b1d4d8cf23df820018197cf75101f Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Thu, 1 Oct 2026 17:22:53 +0200
Subject: [PATCH 2/3] Remove unrelated changes

---
 flang/include/flang/Common/uint128.h | 29 +++++++++++++++-------------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/flang/include/flang/Common/uint128.h b/flang/include/flang/Common/uint128.h
index 4903ddc56558e7..869b64f08c1f78 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -46,15 +46,21 @@ template <bool IS_SIGNED = false> class Int128 {
   constexpr Int128() {}
   // This means of definition provides some portability for
   // "size_t" operands.
-  template <typename T,
-      typename = std::enable_if_t<std::is_integral_v<T> && sizeof(T) <= 8>>
-  constexpr Int128(T n) {
+  constexpr Int128(unsigned n) : low_{n} {}
+  constexpr Int128(unsigned long n) : low_{n} {}
+  constexpr Int128(unsigned long long n) : low_{n} {}
+  constexpr Int128(int n) {
     low_ = static_cast<std::uint64_t>(n);
-    if constexpr (std::is_signed_v<T>) {
-      high_ = -static_cast<std::uint64_t>(n < 0);
-    }
+    high_ = -static_cast<std::uint64_t>(n < 0);
+  }
+  constexpr Int128(long n) {
+    low_ = static_cast<std::uint64_t>(n);
+    high_ = -static_cast<std::uint64_t>(n < 0);
+  }
+  constexpr Int128(long long n) {
+    low_ = static_cast<std::uint64_t>(n);
+    high_ = -static_cast<std::uint64_t>(n < 0);
   }
-
   constexpr Int128(const Int128 &) = default;
   constexpr Int128(Int128 &&) = default;
   constexpr Int128 &operator=(const Int128 &) = default;
@@ -70,12 +76,9 @@ template <bool IS_SIGNED = false> class Int128 {
   constexpr Int128 operator-() const { return ~*this + 1; }
   constexpr bool operator!() const { return !low_ && !high_; }
   constexpr explicit operator bool() const { return low_ || high_; }
-
-  template <typename T,
-      typename = std::enable_if_t<std::is_integral_v<T> && sizeof(T) <= 8>>
-  constexpr explicit operator T() const {
-    return static_cast<T>(low_);
-  }
+  constexpr explicit operator std::uint64_t() const { return low_; }
+  constexpr explicit operator std::int64_t() const { return low_; }
+  constexpr explicit operator int() const { return static_cast<int>(low_); }
 
   constexpr std::uint64_t high() const { return high_; }
   constexpr std::uint64_t low() const { return low_; }

>From 68b6e01f9ae33ee0a86a2c26470f54822fd1c4b4 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Fri, 2 Oct 2026 14:26:45 +0200
Subject: [PATCH 3/3] Handle strict mode libstdc++ as well

Avoid warning

Add unittests

HAS_NATIVE_UINT128_T only in unittest

clang-format
---
 flang/include/flang/Common/uint128.h | 34 +++++++++++++++++++---------
 flang/unittests/Evaluate/uint128.cpp | 30 ++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 11 deletions(-)

diff --git a/flang/include/flang/Common/uint128.h b/flang/include/flang/Common/uint128.h
index 869b64f08c1f78..a00d8c953bb7c7 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -36,8 +36,9 @@ template <typename T> class numeric_limits;
 /// (C++ [namespace.std]), it is not for the MSVC STL __int128/__uint128
 /// workaround below.
 template <typename T>
-using numeric_limits = std::conditional_t<std::is_arithmetic_v<T>,
-    std::numeric_limits<T>, detail::numeric_limits<T>>;
+using numeric_limits =
+    std::conditional_t<std::numeric_limits<T>::is_specialized,
+        std::numeric_limits<T>, detail::numeric_limits<T>>;
 
 template <bool IS_SIGNED = false> class Int128 {
   friend class detail::numeric_limits<Int128>;
@@ -351,13 +352,24 @@ template <> class numeric_limits<Fortran::common::SignedInt128> {
   static constexpr T lowest() { return min(); }
 };
 
-#if defined(__SIZEOF_INT128__) && defined(_MSVC_STL_VERSION)
-// clang-cl knows __int128 and will be used for (u)int128_t, but the MSVC STL
-// does not define stl::numeric_limits for it.
+#if defined(__SIZEOF_INT128__)
+// Handle discrepancy of support bit 128 bit integers by compiler and standard
+// library. The compiler may treat __int128 as a builtin type, but the standard
+// library does not define std::numeric_limits for it. Two cases are known:
+//
+// 1. clang-cl supports __int128, but MSVC, and therefore its STL used by
+//    clang-cl, does not.
+//
+// 2. libstdc++ in strict mode (-std=c++NN) intentionally removes any use of
+//    __int128, even though gcc does not make such a distinction.
+//
+// Note: Using __int128_t/__uint128_t typedefs; spelling out the __int128
+// keyword is a warning "ISO C++ does not support ‘__int128’ for ‘type name’"
+// under -Wpedantic
 
-template <> class numeric_limits<unsigned __int128> {
+template <> class numeric_limits<__uint128_t> {
 public:
-  using T = unsigned __int128;
+  using T = __uint128_t;
 
   static constexpr bool is_specialized{true};
   static constexpr bool is_signed{false};
@@ -368,19 +380,19 @@ template <> class numeric_limits<unsigned __int128> {
   static constexpr T lowest() { return min(); }
 };
 
-template <> class numeric_limits<__int128> {
+template <> class numeric_limits<__int128_t> {
 public:
-  using T = __int128;
+  using T = __int128_t;
 
   static constexpr bool is_specialized{true};
   static constexpr bool is_signed{true};
   static constexpr bool is_integer{true};
 
   static constexpr T min() {
-    return static_cast<T>(static_cast<unsigned __int128>(1) << 127u);
+    return static_cast<T>(static_cast<__uint128_t>(1) << 127u);
   }
   static constexpr T max() {
-    return static_cast<T>(~(static_cast<unsigned __int128>(1) << 127u));
+    return static_cast<T>(~(static_cast<__uint128_t>(1) << 127u));
   }
   static constexpr T lowest() { return min(); }
 };
diff --git a/flang/unittests/Evaluate/uint128.cpp b/flang/unittests/Evaluate/uint128.cpp
index 0b749abe1c080a..6c557c8a57d6cd 100644
--- a/flang/unittests/Evaluate/uint128.cpp
+++ b/flang/unittests/Evaluate/uint128.cpp
@@ -3,6 +3,7 @@
 #include "flang/Testing/testing.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cinttypes>
+#include <limits>
 
 #if (defined __GNUC__ || defined __clang__) && defined __SIZEOF_INT128__
 #define HAS_NATIVE_UINT128_T 1
@@ -113,6 +114,33 @@ static void TestVsNative() {
 }
 #endif
 
+template <typename T> static void TestNumericLimitsEquivalence() {
+  using Std = std::numeric_limits<T>;
+  using Detail = Fortran::common::detail::numeric_limits<T>;
+  using Common = Fortran::common::numeric_limits<T>;
+
+  // Out implementation in Detail must be equivalent to Std (if specialized).
+  if constexpr (Std::is_specialized) {
+    TEST(Detail::is_specialized == Std::is_specialized);
+    TEST(Detail::is_signed == Std::is_signed);
+    TEST(Detail::is_integer == Std::is_integer);
+    TEST(Detail::min() == Std::min());
+    TEST(Detail::max() == Std::max());
+    TEST(Detail::lowest() == Std::lowest());
+  } else {
+    llvm::outs() << "std::numeric_limits is not specialized for this type\n";
+  }
+
+  // Common may refer to either Std to Detail; in either case it must be
+  // identical.
+  TEST(Detail::is_specialized == Common::is_specialized);
+  TEST(Detail::is_integer == Common::is_integer);
+  TEST(Detail::is_signed == Common::is_signed);
+  TEST(Detail::min() == Common::min());
+  TEST(Detail::max() == Common::max());
+  TEST(Detail::lowest() == Common::lowest());
+}
+
 int main() {
   for (std::uint64_t j{0}; j < 64; ++j) {
     Test(j);
@@ -125,6 +153,8 @@ int main() {
 #if HAS_NATIVE_UINT128_T
   llvm::outs() << "Environment has native __uint128_t\n";
   TestVsNative();
+  TestNumericLimitsEquivalence<__int128_t>();
+  TestNumericLimitsEquivalence<__uint128_t>();
 #else
   llvm::outs() << "Environment lacks native __uint128_t\n";
 #endif



More information about the flang-commits mailing list