expm1 low accuracy for complex argument
See original GitHub issueexpm1(1j*x) has the same (low) accuracy for small complex arguments as exp(1j*x) - 1. For real arguments, the accuracy is as expected and on par with numpy:

Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import numexpr as ne
x = np.geomspace(1e-18, 1e-6, 101)
numpy = {}
numpy_naive = {}
numexpr = {}
numexpr['float'] = ne.evaluate('expm1(x)')
numpy['float'] = np.expm1(x)
numpy_naive['float'] = np.exp(x) - 1
numexpr['complex'] = ne.evaluate('expm1(1j*x)')
numpy['complex'] = np.expm1(1j*x)
numpy_naive['complex'] = np.exp(1j*x) - 1
fig, ax = plt.subplots(2, 2, sharex=True, constrained_layout=True)
ax[0, 0].loglog(x, numpy['float'].real, label='numpy')
ax[0, 0].loglog(x, numpy_naive['float'].real, '-.', label='numpy naive')
ax[0, 0].loglog(x, numexpr['float'].real, '--', label='numexpr')
ax[1, 0].loglog(x, abs((numpy['float'] - numexpr['float']).real), label='|np - ne| (expm1)')
ax[0, 0].legend()
ax[1, 0].legend()
ax[0, 0].grid(True)
ax[1, 0].grid(True)
ax[0, 1].loglog(x, -numpy['complex'].real, label='numpy')
ax[0, 1].loglog(x, -numpy_naive['complex'].real, '-.', label='numpy naive')
ax[0, 1].loglog(x, -numexpr['complex'].real, '--', label='numexpr')
ax[1, 1].loglog(x, abs((numpy['complex'] - numexpr['complex']).real), label='|np - ne| (expm1)')
ax[0, 1].legend()
ax[1, 1].legend()
ax[0, 1].grid(True)
ax[1, 1].grid(True)
ax[0, 0].set_title('expm1(x)')
ax[0, 1].set_title('-real(expm1(1j*x))')
ax[1, 0].set_yscale('linear')
ax[1, 0].set_xlabel('x')
ax[1, 1].set_xlabel('x')
Issue Analytics
- State:
- Created a year ago
- Comments:8 (6 by maintainers)
Top Results From Across the Web
Computation of expm1(x) = exp(x) − 1
The exponential function therefore cannot be computed accurately for large x without re- sorting to higher precision, but for small arguments, ...
Read more >Fixed-Accuracy Arithmetic Functions
This chapter describes Intel® IPP fixed-accuracy transcendental mathematical real and complex functions of vector arguments. These functions take an input ...
Read more >Numerical instability for expm1 with complex arguments #14019
The output of np.expm1 becomes unstable for small values (around 10^{-15} to 10^{-16}) when the argument is complex.
Read more >Math library functions that seem unnecessary
This post will give several examples of functions include in the standard C math library that seem unnecessary at first glance.
Read more >Expm1 - Intel
Vector Mathematics computes elementary functions on vector arguments. It can improve performance for applications.
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

Ok, sorry for the long delay but frustrated with the obtuse implementation of NumExpr. I eventually gave up and just overwrote the complex
expfunction to do a comparison. Your function is 25 % slower, but it is indeed significantly more accurate for the real component over a wide range of values. Therefore I have merged your changes into master. I’ll prepare a new release immediately since Python 3.11 is out.The implementation of
expm1xlooks good to me. However, I am noticing some really strange behavior when testing with bothexpm1andexpm1x. Somehow,evaluate('expm1x(z)')gives different results depending on the implementation ofnc_expm1. I.e., if the latter is implemented asthe real part of
expm1xis inaccurate, whereas if it is implemented aseverything is accurate compared to
np.expm1. So it looks likeevaluate('expm1x(z)')actually usesnc_expm1.I am using NumExpr from
condabut don’t use MKL:I tested this on both
MSVC143andg++.