I am new to Numba but decided the best way to learn was to start using it on my existing codebase. I am doing scientific computing with version 0.61.0. I am currently trying to understand why Numba’s jit(nopython=False, fastmath=False) function output doesn’t match vanilla Python. I have narrowed the problem down to the following multiplication (these numbers aren’t arbitrary and are specific to my problem, which is related to generating the associated Legendre functions):
import numpy as np
from numba import jit
@jit(nopython=False, fastmath=False)
def test_mult():
x = 2.372359069868366e-145
BIG = 2**960
print(x * BIG)
test_mult()
>> 0.0 # this is incorrect!
x = 2.372359069868366e-145
BIG = 2**960
x * BIG
>> 2.3119384083660058e+144 # this is correct!
I have tried examining the data types with inspect_types() but don’t know how to proceed in debugging this program. Is this expected Numba behavior?
Numba attempts to convert BIG into an int64, but its value exceeds the int64 limit. Python supports arbitrary-length integers, Numba does not.
from numba import njit
import numpy as np
TINY = 2.372359069868366e-145
BIG = 2**960
np.array(BIG, np.int64)
# OverflowError: Python int too large to convert to C long
np.array(BIG).dtype
# dtype('O'), NumPy converts into Python object (not integer)
@njit
def global_BIG():
return BIG
global_BIG()
# TypingError: Failed in nopython mode pipeline (step: nopython frontend)
# Untyped global name 'BIG': Int value is too large
@njit
def local_BIG():
return 2**960 # int64, BIG > iinfo(int64).max => Overflow
local_BIG()
# 0
To avoid this issue, you can explicitly define BIG as a float64 to help Numba infer the correct type.
@njit
def test_mult():
TINY = 2.372359069868366e-145
BIG = 2**960.
return TINY * BIG
test_mult()
# 2.3119384083660058e+144
This fixed my issues. I didn’t think to inspect this section closer as I (incorrectly) assumed that using a np.float64() on the calculation would enforce the desired typing:
import numpy as np
# code snippet
def func(...)
...
IND = 960 # this is an integer, should be 960.0 to enforce typing
BIG = np.float64(2**IND) # I thought this would be float64
...
return result
It’s actually an interesting example.
Python will not raise an error or produce an incorrect result but it definitely does more work behind the scenes when transforming between Big Integers and Floats.
from timeit import timeit
def fn_BIG():
TINY = 2.372359069868366e-145
IND = 960
BIG = 2**IND
return TINY*BIG
def fn_BIGF():
TINY = 2.372359069868366e-145
IND = 960.
BIG = 2.**IND
return TINY*BIG
size=10_000
(timeit(lambda: sum([fn_BIG() for _ in range(size)]), number=5)
/ timeit(lambda: sum([fn_BIGF() for _ in range(size)]), number=5))
# 7.972739188367083 # => BIGF is 8x faster