Numba simple multiplication giving incorrect result

Hello all!

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?

Thank you!

Hey @woot ,

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
1 Like

Thank you @Oyibo !

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

I learned something new, thank you again!

1 Like

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