[flang-commits] [flang] [llvm] [flang] Fix wrong result for exponentiation when denormal is expected (PR #205262)

via flang-commits flang-commits at lists.llvm.org
Mon Oct 12 01:03:32 PDT 2026


https://github.com/ejose02 updated https://github.com/llvm/llvm-project/pull/205262

>From c530b455638575dc29559828973f74c0f85bbdd4 Mon Sep 17 00:00:00 2001
From: ejose02 <ejose at amd.com>
Date: Tue, 23 Jun 2026 11:13:36 +0530
Subject: [PATCH 1/4] [flang] Fix wrong result for exponentiation when denormal
 is expected

Fixes #192639

Root cause:
Negative integer exponents used exponentiation-by-squaring that divides by successively squared bases; for |base| > 1 intermediates overflow before the final divide.

Fix:
For real types with negative exponent and |base| > 1, evaluate (1/base)^|exp| instead. Apply the same rule in compile-time IntPower and runtime FPowI.
---
 flang-rt/lib/runtime/numeric.cpp       |  4 ++++
 flang-rt/unittests/Runtime/Numeric.cpp |  1 +
 flang/lib/Evaluate/int-power.h         | 18 ++++++++++++++++++
 flang/test/Evaluate/folding07.f90      |  1 +
 4 files changed, 24 insertions(+)

diff --git a/flang-rt/lib/runtime/numeric.cpp b/flang-rt/lib/runtime/numeric.cpp
index 78f148dbc5d8c..fde72c353a3c4 100644
--- a/flang-rt/lib/runtime/numeric.cpp
+++ b/flang-rt/lib/runtime/numeric.cpp
@@ -208,6 +208,10 @@ RT_API_ATTRS BTy FPowI(BTy base, ETy exp) {
   } else if (isNegativePower) {
     exp = -exp;
   }
+  if (isNegativePower && base != BTy{0} && std::abs(base) > BTy{1}) {
+    base = BTy{1} / base;
+    isNegativePower = false;
+  }
   BTy result{1};
   BTy origBase{base};
   while (true) {
diff --git a/flang-rt/unittests/Runtime/Numeric.cpp b/flang-rt/unittests/Runtime/Numeric.cpp
index 4baad3fc9ad0e..aeb6d6d3644f1 100644
--- a/flang-rt/unittests/Runtime/Numeric.cpp
+++ b/flang-rt/unittests/Runtime/Numeric.cpp
@@ -273,6 +273,7 @@ TEST(Numeric, FPowI) {
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{0.5}, Int<4>{-1}), Real<4>{2});
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{-3}, Int<4>{3}), Real<4>{-27});
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{-2}, Int<4>{-3}), Real<4>{-0.125});
+  EXPECT_NE(RTNAME(FPow4i)(Real<4>{2}, Int<4>{-133}), Real<4>{0});
 
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{0}, Int<8>{0}), Real<4>{1});
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{0.3}, Int<8>{0}), Real<4>{1});
diff --git a/flang/lib/Evaluate/int-power.h b/flang/lib/Evaluate/int-power.h
index 2ee012ceb77a3..f9c0f3e9b657e 100644
--- a/flang/lib/Evaluate/int-power.h
+++ b/flang/lib/Evaluate/int-power.h
@@ -15,6 +15,10 @@
 
 namespace Fortran::evaluate {
 
+namespace value {
+template <typename W, int P> class Real;
+}
+
 template <typename REAL, typename INT>
 ValueWithRealFlags<REAL> TimesIntPowerOf(const REAL &factor, const REAL &base,
     const INT &power,
@@ -51,6 +55,20 @@ ValueWithRealFlags<REAL> TimesIntPowerOf(const REAL &factor, const REAL &base,
   return result;
 }
 
+template <typename W, int P, typename INT>
+ValueWithRealFlags<value::Real<W, P>> IntPower(const value::Real<W, P> &base,
+    const INT &power,
+    Rounding rounding = TargetCharacteristics::defaultRounding) {
+  using REAL = value::Real<W, P>;
+  REAL one{REAL::FromInteger(INT{1}).value};
+  if (power.IsNegative() && !base.IsZero() &&
+      base.ABS().Compare(one) == Relation::Greater) {
+    REAL recip{one.Divide(base, rounding).value};
+    return TimesIntPowerOf(one, recip, power.ABS().value, rounding);
+  }
+  return TimesIntPowerOf(one, base, power, rounding);
+}
+
 template <typename REAL, typename INT>
 ValueWithRealFlags<REAL> IntPower(const REAL &base, const INT &power,
     Rounding rounding = TargetCharacteristics::defaultRounding) {
diff --git a/flang/test/Evaluate/folding07.f90 b/flang/test/Evaluate/folding07.f90
index d51df7acf7b8a..0952ae7b34847 100644
--- a/flang/test/Evaluate/folding07.f90
+++ b/flang/test/Evaluate/folding07.f90
@@ -307,5 +307,6 @@ module m
   logical, parameter :: test_set_exponent_1 = set_exponent(1., 1) == 1.
   logical, parameter :: test_set_exponent_2 = set_exponent(1., 2) == 2.
   logical, parameter :: test_set_exponent_min = set_exponent(1., -149) == 1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125e-45_4
+  logical, parameter :: test_pow_denorm_neg = 2.0**(-133) /= 0.0
 
 end module

>From 8deba6c60581a8932ebe5782a9bf3d7702078555 Mon Sep 17 00:00:00 2001
From: ejose <ejose at amd.com>
Date: Thu, 8 Oct 2026 11:18:30 +0000
Subject: [PATCH 2/4] Use ABSTy in FPowI and propagate IntPower divide flags

---
 flang-rt/lib/runtime/numeric.cpp | 2 +-
 flang/lib/Evaluate/int-power.h   | 7 +++++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/flang-rt/lib/runtime/numeric.cpp b/flang-rt/lib/runtime/numeric.cpp
index fde72c353a3c4..272fee336d18d 100644
--- a/flang-rt/lib/runtime/numeric.cpp
+++ b/flang-rt/lib/runtime/numeric.cpp
@@ -208,7 +208,7 @@ RT_API_ATTRS BTy FPowI(BTy base, ETy exp) {
   } else if (isNegativePower) {
     exp = -exp;
   }
-  if (isNegativePower && base != BTy{0} && std::abs(base) > BTy{1}) {
+  if (isNegativePower && base != BTy{0} && ABSTy<BTy>::compute(base) > BTy{1}) {
     base = BTy{1} / base;
     isNegativePower = false;
   }
diff --git a/flang/lib/Evaluate/int-power.h b/flang/lib/Evaluate/int-power.h
index f9c0f3e9b657e..c3d8e5ae1aa31 100644
--- a/flang/lib/Evaluate/int-power.h
+++ b/flang/lib/Evaluate/int-power.h
@@ -63,8 +63,11 @@ ValueWithRealFlags<value::Real<W, P>> IntPower(const value::Real<W, P> &base,
   REAL one{REAL::FromInteger(INT{1}).value};
   if (power.IsNegative() && !base.IsZero() &&
       base.ABS().Compare(one) == Relation::Greater) {
-    REAL recip{one.Divide(base, rounding).value};
-    return TimesIntPowerOf(one, recip, power.ABS().value, rounding);
+    ValueWithRealFlags<REAL> result;
+    REAL recip{one.Divide(base, rounding).AccumulateFlags(result.flags)};
+    result.value = TimesIntPowerOf(one, recip, power.ABS().value, rounding)
+                       .AccumulateFlags(result.flags);
+    return result;
   }
   return TimesIntPowerOf(one, base, power, rounding);
 }

>From f0c1f50558d3b8a0116e97dd5ae0fec4c06db104 Mon Sep 17 00:00:00 2001
From: ejose <ejose at amd.com>
Date: Fri, 9 Oct 2026 11:32:58 +0000
Subject: [PATCH 3/4] [flang] Overflow-gated IntPower retry and stronger pow
 tests

---
 flang-rt/unittests/Runtime/Numeric.cpp |  9 +++++++-
 flang/lib/Evaluate/int-power.h         | 30 +++++++++++++-------------
 flang/test/Evaluate/folding07.f90      |  5 ++++-
 3 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/flang-rt/unittests/Runtime/Numeric.cpp b/flang-rt/unittests/Runtime/Numeric.cpp
index aeb6d6d3644f1..ba8b9ae5f2300 100644
--- a/flang-rt/unittests/Runtime/Numeric.cpp
+++ b/flang-rt/unittests/Runtime/Numeric.cpp
@@ -273,7 +273,10 @@ TEST(Numeric, FPowI) {
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{0.5}, Int<4>{-1}), Real<4>{2});
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{-3}, Int<4>{3}), Real<4>{-27});
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{-2}, Int<4>{-3}), Real<4>{-0.125});
-  EXPECT_NE(RTNAME(FPow4i)(Real<4>{2}, Int<4>{-133}), Real<4>{0});
+  EXPECT_EQ(
+      RTNAME(FPow4i)(Real<4>{2}, Int<4>{-133}), std::ldexp(Real<4>{1}, -133));
+  EXPECT_EQ(RTNAME(FPow4i)(Real<4>{2}, std::numeric_limits<Int<4>>::min()),
+      Real<4>{0});
 
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{0}, Int<8>{0}), Real<4>{1});
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{0.3}, Int<8>{0}), Real<4>{1});
@@ -281,6 +284,8 @@ TEST(Numeric, FPowI) {
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{0.5}, Int<8>{-1}), Real<4>{2});
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{-3}, Int<8>{3}), Real<4>{-27});
   EXPECT_EQ(RTNAME(FPow4k)(Real<4>{-2}, Int<8>{-3}), Real<4>{-0.125});
+  EXPECT_EQ(
+      RTNAME(FPow4k)(Real<4>{2}, Int<8>{-133}), std::ldexp(Real<4>{1}, -133));
 
   EXPECT_EQ(RTNAME(FPow8i)(Real<8>{0}, Int<4>{0}), Real<8>{1});
   EXPECT_EQ(RTNAME(FPow8i)(Real<8>{0.3}, Int<4>{0}), Real<8>{1});
@@ -288,6 +293,8 @@ TEST(Numeric, FPowI) {
   EXPECT_EQ(RTNAME(FPow8i)(Real<8>{0.5}, Int<4>{-1}), Real<8>{2});
   EXPECT_EQ(RTNAME(FPow8i)(Real<8>{-3}, Int<4>{3}), Real<8>{-27});
   EXPECT_EQ(RTNAME(FPow8i)(Real<8>{-2}, Int<4>{-3}), Real<8>{-0.125});
+  EXPECT_EQ(
+      RTNAME(FPow8i)(Real<8>{2}, Int<4>{-1070}), std::ldexp(Real<8>{1}, -1070));
 
   EXPECT_EQ(RTNAME(FPow8k)(Real<8>{0}, Int<8>{0}), Real<8>{1});
   EXPECT_EQ(RTNAME(FPow8k)(Real<8>{0.3}, Int<8>{0}), Real<8>{1});
diff --git a/flang/lib/Evaluate/int-power.h b/flang/lib/Evaluate/int-power.h
index c3d8e5ae1aa31..3f9aa1b1612a1 100644
--- a/flang/lib/Evaluate/int-power.h
+++ b/flang/lib/Evaluate/int-power.h
@@ -11,17 +11,14 @@
 
 // Computes an integer power of a real or complex value.
 
+#include "flang/Evaluate/real.h"
 #include "flang/Evaluate/target.h"
 
 namespace Fortran::evaluate {
 
-namespace value {
-template <typename W, int P> class Real;
-}
-
 template <typename REAL, typename INT>
 ValueWithRealFlags<REAL> TimesIntPowerOf(const REAL &factor, const REAL &base,
-    const INT &power,
+    const INT &power, bool forceMultiply = false,
     Rounding rounding = TargetCharacteristics::defaultRounding) {
   ValueWithRealFlags<REAL> result{factor};
   if (base.IsNotANumber()) {
@@ -32,7 +29,7 @@ ValueWithRealFlags<REAL> TimesIntPowerOf(const REAL &factor, const REAL &base,
       result.flags.set(RealFlag::InvalidArgument);
     }
   } else {
-    bool negativePower{power.IsNegative()};
+    bool negativePower{!forceMultiply && power.IsNegative()};
     INT absPower{power.ABS().value};
     REAL squares{base};
     int nbits{INT::bits - absPower.LEADZ()};
@@ -61,22 +58,25 @@ ValueWithRealFlags<value::Real<W, P>> IntPower(const value::Real<W, P> &base,
     Rounding rounding = TargetCharacteristics::defaultRounding) {
   using REAL = value::Real<W, P>;
   REAL one{REAL::FromInteger(INT{1}).value};
-  if (power.IsNegative() && !base.IsZero() &&
-      base.ABS().Compare(one) == Relation::Greater) {
-    ValueWithRealFlags<REAL> result;
-    REAL recip{one.Divide(base, rounding).AccumulateFlags(result.flags)};
-    result.value = TimesIntPowerOf(one, recip, power.ABS().value, rounding)
-                       .AccumulateFlags(result.flags);
-    return result;
+  ValueWithRealFlags<REAL> result{
+      TimesIntPowerOf(one, base, power, /*forceMultiply=*/false, rounding)};
+  if (result.flags.test(RealFlag::Overflow) && power.IsNegative() &&
+      !base.IsZero() && base.ABS().Compare(one) == Relation::Greater) {
+    ValueWithRealFlags<REAL> retry;
+    REAL recip{one.Divide(base, rounding).AccumulateFlags(retry.flags)};
+    retry.value =
+        TimesIntPowerOf(one, recip, power, /*forceMultiply=*/true, rounding)
+            .AccumulateFlags(retry.flags);
+    return retry;
   }
-  return TimesIntPowerOf(one, base, power, rounding);
+  return result;
 }
 
 template <typename REAL, typename INT>
 ValueWithRealFlags<REAL> IntPower(const REAL &base, const INT &power,
     Rounding rounding = TargetCharacteristics::defaultRounding) {
   REAL one{REAL::FromInteger(INT{1}).value};
-  return TimesIntPowerOf(one, base, power, rounding);
+  return TimesIntPowerOf(one, base, power, /*forceMultiply=*/false, rounding);
 }
 } // namespace Fortran::evaluate
 #endif // FORTRAN_EVALUATE_INT_POWER_H_
diff --git a/flang/test/Evaluate/folding07.f90 b/flang/test/Evaluate/folding07.f90
index 0952ae7b34847..8fc0a20f246eb 100644
--- a/flang/test/Evaluate/folding07.f90
+++ b/flang/test/Evaluate/folding07.f90
@@ -307,6 +307,9 @@ module m
   logical, parameter :: test_set_exponent_1 = set_exponent(1., 1) == 1.
   logical, parameter :: test_set_exponent_2 = set_exponent(1., 2) == 2.
   logical, parameter :: test_set_exponent_min = set_exponent(1., -149) == 1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125e-45_4
-  logical, parameter :: test_pow_denorm_neg = 2.0**(-133) /= 0.0
+  logical, parameter :: test_pow_subnormal_4 = 2.0_4**(-133) == scale(1.0_4, -133)
+  logical, parameter :: test_pow_neg_3 = 3.0**(-2) == 1.0/3.0**2
+  logical, parameter :: test_pow_neg_7 = 7.0**(-5) == 1.0/7.0**5
+  logical, parameter :: test_pow_min_int1 = 1.5_8**(-128_1) < 1.0_8
 
 end module

>From c3d5b573f8612583c5bfc0ae5ac46d16c9ade163 Mon Sep 17 00:00:00 2001
From: ejose <ejose at amd.com>
Date: Mon, 12 Oct 2026 07:19:51 +0000
Subject: [PATCH 4/4] [flang][flang-rt] Gate FPowI retry on zero quotient;
 extend pow fold tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Retry from 1/base only when a negative power divides to zero, so
subnormal results are preserved without the 1–3 ULP loss from always
reciprocating |base|>1. Drop ABSTy from FPowI. Add folding and unit
tests from review, including the IntPower retry case.
---
 flang-rt/lib/runtime/numeric.cpp       | 35 +++++++++++++++-----------
 flang-rt/unittests/Runtime/Numeric.cpp |  3 +++
 flang/test/Evaluate/folding07.f90      |  7 ++++++
 3 files changed, 30 insertions(+), 15 deletions(-)

diff --git a/flang-rt/lib/runtime/numeric.cpp b/flang-rt/lib/runtime/numeric.cpp
index 272fee336d18d..c94f16ac72adc 100644
--- a/flang-rt/lib/runtime/numeric.cpp
+++ b/flang-rt/lib/runtime/numeric.cpp
@@ -196,22 +196,8 @@ inline RT_API_ATTRS T Nearest(T x, bool positive) {
   }
 }
 
-// Exponentiation operator for (Real ** Integer) cases (10.1.5.2.1).
 template <typename BTy, typename ETy>
-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()};
-  if (isMinPower) {
-    exp = std::numeric_limits<ETy>::max();
-  } else if (isNegativePower) {
-    exp = -exp;
-  }
-  if (isNegativePower && base != BTy{0} && ABSTy<BTy>::compute(base) > BTy{1}) {
-    base = BTy{1} / base;
-    isNegativePower = false;
-  }
+RT_API_ATTRS BTy FPowIPositive(BTy base, ETy exp, bool isMinPower) {
   BTy result{1};
   BTy origBase{base};
   while (true) {
@@ -227,8 +213,27 @@ RT_API_ATTRS BTy FPowI(BTy base, ETy exp) {
   if (isMinPower) {
     result *= origBase;
   }
+  return result;
+}
+
+// Exponentiation operator for (Real ** Integer) cases (10.1.5.2.1).
+template <typename BTy, typename ETy>
+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()};
+  if (isMinPower) {
+    exp = std::numeric_limits<ETy>::max();
+  } else if (isNegativePower) {
+    exp = -exp;
+  }
+  BTy result{FPowIPositive(base, exp, isMinPower)};
   if (isNegativePower) {
     result = BTy{1} / result;
+    if (result == BTy{0} && base != BTy{0}) {
+      result = FPowIPositive(BTy{1} / base, exp, isMinPower);
+    }
   }
   return result;
 }
diff --git a/flang-rt/unittests/Runtime/Numeric.cpp b/flang-rt/unittests/Runtime/Numeric.cpp
index ba8b9ae5f2300..b46570ec204c3 100644
--- a/flang-rt/unittests/Runtime/Numeric.cpp
+++ b/flang-rt/unittests/Runtime/Numeric.cpp
@@ -273,6 +273,9 @@ TEST(Numeric, FPowI) {
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{0.5}, Int<4>{-1}), Real<4>{2});
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{-3}, Int<4>{3}), Real<4>{-27});
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{-2}, Int<4>{-3}), Real<4>{-0.125});
+  EXPECT_EQ(RTNAME(FPow4i)(Real<4>{3}, Int<4>{-2}), Real<4>{1} / Real<4>{9});
+  EXPECT_EQ(
+      RTNAME(FPow4i)(Real<4>{7}, Int<4>{-5}), Real<4>{1} / Real<4>{16807});
   EXPECT_EQ(
       RTNAME(FPow4i)(Real<4>{2}, Int<4>{-133}), std::ldexp(Real<4>{1}, -133));
   EXPECT_EQ(RTNAME(FPow4i)(Real<4>{2}, std::numeric_limits<Int<4>>::min()),
diff --git a/flang/test/Evaluate/folding07.f90 b/flang/test/Evaluate/folding07.f90
index 8fc0a20f246eb..d9e4a2c31cc81 100644
--- a/flang/test/Evaluate/folding07.f90
+++ b/flang/test/Evaluate/folding07.f90
@@ -308,8 +308,15 @@ module m
   logical, parameter :: test_set_exponent_2 = set_exponent(1., 2) == 2.
   logical, parameter :: test_set_exponent_min = set_exponent(1., -149) == 1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125e-45_4
   logical, parameter :: test_pow_subnormal_4 = 2.0_4**(-133) == scale(1.0_4, -133)
+  logical, parameter :: test_pow_subnormal_8 = 2.0_8**(-1070) == scale(1.0_8, -1070)
+  logical, parameter :: test_pow_subnormal_16 = 2.0_16**(-16400) == scale(1.0_16, -16400)
   logical, parameter :: test_pow_neg_3 = 3.0**(-2) == 1.0/3.0**2
   logical, parameter :: test_pow_neg_7 = 7.0**(-5) == 1.0/7.0**5
+  logical, parameter :: test_pow_neg_10 = 10.0**(-2) == 1.0/10.0**2
+  logical, parameter :: test_pow_neg_2_5 = 2.5**(-9) == 1.0/2.5**9
   logical, parameter :: test_pow_min_int1 = 1.5_8**(-128_1) < 1.0_8
+  logical, parameter :: test_pow_min_int1_retry = 2.0_4**(-128_1) == scale(1.0_4, -128)
+  logical, parameter :: test_pow_min_int2 = 1.0001_8**(-32768_2) < 1.0_8
+  logical, parameter :: test_pow_min_int4 = 1.0000001_8**(-huge(1)-1) < 1.0_8
 
 end module



More information about the flang-commits mailing list