Repository: numba/numba

Issue:
Error in np.sqrt for large negative values
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

When evaluating the squareroot of a complex number with large negative values, numba produces a wrong number. The minimal example below shows, that the error starts kicking in for numbers starting with 10**-38 and the resulting value is off the exact number by a factor of 0.5. 

## Minimal example
![figure_1](https://user-images.githubusercontent.com/8895501/48503980-f2471e00-e843-11e8-8881-f500bbb13868.png)


```python
import numba
import numpy as np
from matplotlib import pyplot as plt

def squareroot(a):
    return np.sqrt(a)

@numba.jit(nopython=True)
def squareroot_numba(a):
    return np.sqrt(a)

#%%
# create negative complex values
exponents = np.linspace(-10, 100, num=111)
test_vals = - np.array(10**exponents, dtype=complex)

fig, ax = plt.subplots(2, sharex=True)
ax[0].semilogy(exponents, -squareroot(test_vals).imag)
ax[0].plot(exponents, -squareroot_numba(test_vals).imag)
ax[0].grid()
ax[0].set(ylabel='Im(sqrt(-10**x))')

ratio = (squareroot(test_vals) / squareroot_numba(test_vals)).real
ax[1].plot(exponents, ratio, 'x-')
ax[1].set(title='Ratio of sqare roots computed with numpy vs. numba',
          xlabel='exponent to basis 10', ylabel='ratio')
```



<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I am using the latest released version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->


Discussion:
Thanks for the report. Seems like you are seeing errors appearing from values `-1e38` and above in magnitude, which is suspiciously near the limit of 32bit float representation. We'll take a look.

Resolving patch:
diff --git a/numba/targets/cmathimpl.py b/numba/targets/cmathimpl.py
--- a/numba/targets/cmathimpl.py
+++ b/numba/targets/cmathimpl.py
@@ -207,7 +207,17 @@ def polar_impl(x, y, x_is_finite, y_is_finite):
 @lower(cmath.sqrt, types.Complex)
 def sqrt_impl(context, builder, sig, args):
     # We risk spurious overflow for components >= FLT_MAX / (1 + sqrt(2)).
-    THRES = mathimpl.FLT_MAX / (1 + math.sqrt(2))
+
+    SQRT2 = 1.414213562373095048801688724209698079E0
+    ONE_PLUS_SQRT2 = (1. + SQRT2)
+    theargflt = sig.args[0].underlying_float
+    # Get a type specific maximum value so scaling for overflow is based on that
+    MAX = mathimpl.DBL_MAX if theargflt.bitwidth == 64 else mathimpl.FLT_MAX
+    # THRES will be double precision, should not impact typing as it's just
+    # used for comparison, there *may* be a few values near THRES which
+    # deviate from e.g. NumPy due to rounding that occurs in the computation
+    # of this value in the case of a 32bit argument.
+    THRES = MAX / ONE_PLUS_SQRT2
 
     def sqrt_impl(z):
         """cmath.sqrt(z)"""
diff --git a/numba/targets/mathimpl.py b/numba/targets/mathimpl.py
--- a/numba/targets/mathimpl.py
+++ b/numba/targets/mathimpl.py
@@ -6,6 +6,7 @@
 import math
 import operator
 import sys
+import numpy as np
 
 import llvmlite.llvmpy.core as lc
 from llvmlite.llvmpy.core import Type
@@ -20,9 +21,13 @@
 
 
 # Helpers, shared with cmathimpl.
+_NP_FLT_FINFO = np.finfo(np.dtype('float32'))
+FLT_MAX = _NP_FLT_FINFO.max
+FLT_MIN = _NP_FLT_FINFO.tiny
 
-FLT_MAX = 3.402823466E+38
-FLT_MIN = 1.175494351E-38
+_NP_DBL_FINFO = np.finfo(np.dtype('float64'))
+DBL_MAX = _NP_DBL_FINFO.max
+DBL_MIN = _NP_DBL_FINFO.tiny
 
 FLOAT_ABS_MASK = 0x7fffffff
 FLOAT_SIGN_MASK = 0x80000000


Test patch:
diff --git a/numba/tests/test_complex.py b/numba/tests/test_complex.py
--- a/numba/tests/test_complex.py
+++ b/numba/tests/test_complex.py
@@ -234,6 +234,11 @@ def test_sqrt(self):
 
     def test_sqrt_npm(self):
         self.check_unary_func(sqrt_usecase, no_pyobj_flags)
+        # issue #3499, check large complex128 values, values cross the float32
+        # representation limit threshold
+        values = [-10 ** i for i in range(36, 41)]
+        self.run_unary(sqrt_usecase, [types.complex128],
+                       values, flags=no_pyobj_flags)
 
     # Trigonometric functions