Skip to content

Conversation

@nurmukhametov
Copy link
Contributor

@nurmukhametov nurmukhametov commented Oct 27, 2025

When computing exp(x + iy) with large x and small y, the naive computation exp(x) * (cos(y) + i*sin(y)) produces inf when exp(x) overflows, even though the result is finite when y is sufficiently small. Fix this with exp(x/2) * (cos(y) + i*sin(y)) * exp(x/2)

@nurmukhametov nurmukhametov requested a review from a team as a code owner October 27, 2025 13:59
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Oct 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 27, 2025

@llvm/pr-subscribers-libcxx

Author: Aleksei Nurmukhametov (nurmukhametov)

Changes

When computing exp(x + iy) with large x and small y, the naive computation exp(x) * (cos(y) + i*sin(y)) produces inf when exp(x) overflows, even though the result is finite when y is sufficiently small. Fix this with exp(x/2) * (cos(y) + i*sin(y)) * exp(x/2)


Full diff: https://github.com/llvm/llvm-project/pull/165254.diff

2 Files Affected:

  • (modified) libcxx/include/complex (+4)
  • (added) libcxx/test/libcxx/numerics/complex.number/exp.pass.cpp (+44)
diff --git a/libcxx/include/complex b/libcxx/include/complex
index d8ec3d95c10ed..c749156e5d4f5 100644
--- a/libcxx/include/complex
+++ b/libcxx/include/complex
@@ -1091,6 +1091,10 @@ _LIBCPP_HIDE_FROM_ABI complex<_Tp> exp(const complex<_Tp>& __x) {
     }
   }
   _Tp __e = std::exp(__x.real());
+  if (std::isinf(__e)) {
+    _Tp __e2 = std::exp(__x.real() * _Tp(0.5));
+    return complex<_Tp>(__e2 * std::cos(__i) * __e2, __e2 * std::sin(__i) * __e2);
+  }
   return complex<_Tp>(__e * std::cos(__i), __e * std::sin(__i));
 }
 
diff --git a/libcxx/test/libcxx/numerics/complex.number/exp.pass.cpp b/libcxx/test/libcxx/numerics/complex.number/exp.pass.cpp
new file mode 100644
index 0000000000000..74c4a1887a8ec
--- /dev/null
+++ b/libcxx/test/libcxx/numerics/complex.number/exp.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <complex>
+
+// template<class T>
+//   complex<T>
+//   exp(const complex<T>& x);
+//
+// Tests for libc++-specific overflow handling behavior in complex exponential.
+// These tests validate implementation-specific handling of edge cases where
+// exp(real_part) overflows but the result should still be well-defined.
+
+#include <complex>
+#include <cassert>
+#include <cmath>
+
+#include "test_macros.h"
+
+template <class T>
+void test_overflow_case() {
+    typedef std::complex<T> C;
+
+    // In this case, the overflow of exp(real_part) is compensated when
+    // sin(imag_part) is close to zero, resulting in a finite imaginary part.
+    C z(T(90.0238094), T(5.900613e-39));
+    C result = std::exp(z);
+
+    assert(std::isinf(result.real()));
+    assert(result.real() > 0);
+
+    assert(std::isfinite(result.imag()));
+    assert(std::abs(result.imag() - T(7.3746)) < T(1.0));
+}
+
+int main(int, char**) {
+    test_overflow_case<float>();
+    return 0;
+}

@github-actions
Copy link

github-actions bot commented Oct 27, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

When computing exp(x + iy) with large x and small y, the naive
computation exp(x) * (cos(y) + i*sin(y)) produces inf when exp(x)
overflows, even though the result is finite when y is sufficiently
small. Fix this with exp(x/2) * (cos(y) + i*sin(y)) * exp(x/2)
@nurmukhametov nurmukhametov force-pushed the fix-libcxx-complex-exp-accuracy branch from 3c3fc82 to 1c7d2be Compare October 27, 2025 14:02
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an approach similar to #99677 would be better.

@nurmukhametov
Copy link
Contributor Author

I think an approach similar to #99677 would be better.

I didn't know about it. I can do the same, but I wonder why it hasn't been merged yet.

@philnik777
Copy link
Contributor

I think an approach similar to #99677 would be better.

I didn't know about it. I can do the same, but I wonder why it hasn't been merged yet.

IIRC it's mostly due to me having no idea what I'm doing with floating point numbers and the CI complaining in some place. Anybody who know anything about them probably knows what to do. (So if you want to after this patch, feel free to take a look that the rest of the functions. That would be very much appreciated.)

@nurmukhametov
Copy link
Contributor Author

I think an approach similar to #99677 would be better.

I have created this PR #165457 with this approach. However, I don't really understand why you preserved partially the initial implementation in #99677.

@nurmukhametov
Copy link
Contributor Author

I think an approach similar to #99677 would be better.

This approach (calling implementations of underlying libm) doesn't fix the initial issue (the accuracy of complex exponential implementation) the current PR addressing. You can see it in #165457 for example for CI jobs with picolibc.

@philnik777
Copy link
Contributor

I think an approach similar to #99677 would be better.

This approach (calling implementations of underlying libm) doesn't fix the initial issue (the accuracy of complex exponential implementation) the current PR addressing. You can see it in #165457 for example for CI jobs with picolibc.

I don't think that's a huge problem. We're really not in the business of implementing high quality math functions (see <complex>). That's the kind of stuff we should leave to the libc. We wouldn't implement any other math functions just because some libc does a sub-par job. If your libc doesn't provide a high quality enough implementation for your purposes you can file a bug against that or use a LLVM libc shim instead (once they implement the complex family of functions).

@nurmukhametov
Copy link
Contributor Author

I think an approach similar to #99677 would be better.

This approach (calling implementations of underlying libm) doesn't fix the initial issue (the accuracy of complex exponential implementation) the current PR addressing. You can see it in #165457 for example for CI jobs with picolibc.

I don't think that's a huge problem. We're really not in the business of implementing high quality math functions (see <complex>). That's the kind of stuff we should leave to the libc. We wouldn't implement any other math functions just because some libc does a sub-par job. If your libc doesn't provide a high quality enough implementation for your purposes you can file a bug against that or use a LLVM libc shim instead (once they implement the complex family of functions).

I understand this point, but it is not so currently. At the moment, libcxx has its own implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants