Repository: numba/numba

Issue:
np.iinfo and np.finfo
Supporting `np.iinfo` and `np.finfo` which provide machine limits for data types would be useful. My current use case is write reduction functions.


Discussion:

Resolving patch:
diff --git a/numba/targets/arraymath.py b/numba/targets/arraymath.py
--- a/numba/targets/arraymath.py
+++ b/numba/targets/arraymath.py
@@ -5,6 +5,7 @@
 from __future__ import print_function, absolute_import, division
 
 import math
+from collections import namedtuple
 
 import numpy as np
 
@@ -1372,3 +1373,50 @@ def histogram_impl(a, bins, range=None):
             return hist, bins
 
     return histogram_impl
+
+
+# Create np.finfo, np.iinfo and np.MachAr
+# machar
+_mach_ar_supported = ('ibeta', 'it', 'machep', 'eps', 'negep', 'epsneg',
+                      'iexp', 'minexp', 'xmin', 'maxexp', 'xmax', 'irnd',
+                      'ngrd', 'epsilon', 'tiny', 'huge', 'precision',
+                      'resolution',)
+MachAr = namedtuple('MachAr', _mach_ar_supported)
+
+# Do not support MachAr field
+# finfo
+_finfo_supported = ('eps', 'epsneg', 'iexp', 'machep', 'max', 'maxexp', 'min',
+                    'minexp', 'negep', 'nexp', 'nmant', 'precision',
+                    'resolution', 'tiny',)
+if numpy_version >= (1, 12):
+    _finfo_supported = ('bits',) + _finfo_supported
+
+finfo = namedtuple('finfo', _finfo_supported)
+
+# iinfo
+_iinfo_supported = ('min', 'max')
+if numpy_version >= (1, 12):
+    _iinfo_supported = _iinfo_supported + ('bits',)
+
+iinfo = namedtuple('iinfo', _iinfo_supported)
+
+@overload(np.MachAr)
+def MachAr_impl():
+    f = np.MachAr()
+    _mach_ar_data = tuple([getattr(f, x) for x in _mach_ar_supported])
+    def impl():
+        return MachAr(*_mach_ar_data)
+    return impl
+
+def generate_xinfo(np_func, container, attr):
+    @overload(np_func)
+    def xinfo_impl(arg):
+        nbty = getattr(arg, 'dtype', arg)
+        f = np_func(as_dtype(nbty))
+        data = tuple([getattr(f, x) for x in attr])
+        def impl(arg):
+            return container(*data)
+        return impl
+
+generate_xinfo(np.finfo, finfo, _finfo_supported)
+generate_xinfo(np.iinfo, iinfo, _iinfo_supported)


Test patch:
diff --git a/numba/tests/test_np_functions.py b/numba/tests/test_np_functions.py
--- a/numba/tests/test_np_functions.py
+++ b/numba/tests/test_np_functions.py
@@ -11,6 +11,7 @@
 from numba.compiler import compile_isolated, Flags, utils
 from numba import jit, typeof, types
 from numba.numpy_support import version as np_version
+from numba.errors import UntypedAttributeError
 from .support import TestCase, CompilationCache
 
 no_pyobj_flags = Flags()
@@ -47,6 +48,18 @@ def digitize(*args):
 def histogram(*args):
     return np.histogram(*args)
 
+def machar(*args):
+    return np.MachAr()
+
+def iinfo(*args):
+    return np.iinfo(*args)
+
+def finfo(*args):
+    return np.finfo(*args)
+
+def finfo_machar(*args):
+    return np.finfo(*args).machar
+
 
 class TestNPFunctions(TestCase):
     """
@@ -410,3 +423,78 @@ def check_values(values):
         self.rnd.shuffle(values)
 
         check_values(values)
+
+class TestNPMachineParameters(TestCase):
+    # tests np.finfo, np.iinfo, np.MachAr
+
+    template = '''
+def foo():
+    ty = np.%s
+    return np.%s(ty)
+'''
+
+    bits = ('bits',) if np_version >= (1, 12)  else ()
+
+    def check(self, func, attrs, *args):
+        pyfunc = func
+        cfunc = jit(nopython=True)(pyfunc)
+
+        expected = pyfunc(*args)
+        got = cfunc(*args)
+
+        # check result
+        for attr in attrs:
+            self.assertPreciseEqual(getattr(expected, attr),
+                                    getattr(got, attr))
+
+    def create_harcoded_variant(self, basefunc, ty):
+        #create an instance of using the function with a hardcoded type
+        #and eval it into existence, return the function for use
+        tystr = ty.__name__
+        basestr = basefunc.__name__
+        funcstr = self.template % (tystr, basestr)
+        eval(compile(funcstr, '<string>', 'exec'))
+        return locals()['foo']
+
+    def test_MachAr(self):
+        attrs = ('ibeta', 'it', 'machep', 'eps', 'negep', 'epsneg', 'iexp',
+                 'minexp', 'xmin', 'maxexp', 'xmax', 'irnd', 'ngrd',
+                 'epsilon', 'tiny', 'huge', 'precision', 'resolution',)
+        self.check(machar, attrs)
+
+    def test_finfo(self):
+        types = [np.float32, np.float64, np.complex64, np.complex128]
+        attrs = self.bits + ('eps', 'epsneg', 'iexp', 'machep', 'max',
+                'maxexp', 'negep', 'nexp', 'nmant', 'precision',
+                'resolution', 'tiny',)
+        for ty in types:
+            self.check(finfo, attrs, ty(1))
+            hc_func = self.create_harcoded_variant(np.finfo, ty)
+            self.check(hc_func, attrs)
+
+        # check unsupported attr raises
+        with self.assertRaises(UntypedAttributeError) as raises:
+            cfunc = jit(nopython=True)(finfo_machar)
+            cfunc(7.)
+        msg = "Unknown attribute 'machar' of type finfo"
+        self.assertIn(msg, str(raises.exception))
+
+        # check invalid type raises
+        with self.assertTypingError():
+            cfunc = jit(nopython=True)(finfo)
+            cfunc(np.int32(7))
+
+    def test_iinfo(self):
+        # check types and instances of types
+        types = [np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16,
+                 np.uint32, np.uint64]
+        attrs = ('min', 'max') + self.bits
+        for ty in types:
+            self.check(iinfo, attrs, ty(1))
+            hc_func = self.create_harcoded_variant(np.iinfo, ty)
+            self.check(hc_func, attrs)
+
+        # check invalid type raises
+        with self.assertTypingError():
+            cfunc = jit(nopython=True)(iinfo)
+            cfunc(np.float64(7))