Repository: numba/numba

Issue:
math.trunc should return int, not float
```
>>> def f(x): return math.trunc(x)
...
>>> f(-10.6)
-10
>>> jit(f)(-10.6)
-10.0
```



Discussion:
Same for math.ceil and math.floor, it turns out.


Resolving patch:
diff --git a/numba/targets/mathimpl.py b/numba/targets/mathimpl.py
--- a/numba/targets/mathimpl.py
+++ b/numba/targets/mathimpl.py
@@ -56,26 +56,30 @@ def f64impl(context, builder, sig, args):
     unary_math_int_impl(fn, f64impl)
 
 
-def unary_math_extern(fn, f32extern, f64extern):
-    @register
-    @implement(fn, types.float32)
-    def f32impl(context, builder, sig, args):
-        [val] = args
-        mod = cgutils.get_module(builder)
-        fnty = Type.function(Type.float(), [Type.float()])
-        fn = mod.get_or_insert_function(fnty, name=f32extern)
-        return builder.call(fn, (val,))
+def unary_math_extern(fn, f32extern, f64extern, restype=None):
 
-    @register
-    @implement(fn, types.float64)
-    def f64impl(context, builder, sig, args):
-        [val] = args
-        mod = cgutils.get_module(builder)
-        fnty = Type.function(Type.double(), [Type.double()])
-        fn = mod.get_or_insert_function(fnty, name=f64extern)
-        return builder.call(fn, (val,))
+    def float_input_impl(extern_func, input_type):
+        def implementer(context, builder, sig, args):
+            [val] = args
+            mod = cgutils.get_module(builder)
+            lty = context.get_value_type(input_type)
+            fnty = Type.function(lty, [lty])
+            fn = mod.get_or_insert_function(fnty, name=extern_func)
+            res = builder.call(fn, (val,))
+            if restype is None:
+                return res
+            else:
+                return context.cast(builder, res, input_type,
+                                    restype)
 
-    unary_math_int_impl(fn, f64impl)
+        return implementer
+
+    register(implement(fn, types.float32)
+             (float_input_impl(f32extern, types.float32)))
+    register(implement(fn, types.float64)
+             (float_input_impl(f64extern, types.float64)))
+
+    unary_math_int_impl(fn, float_input_impl(f64extern, types.float64))
 
 
 unary_math_intr(math.fabs, lc.INTR_FABS)
@@ -101,10 +105,15 @@ def f64impl(context, builder, sig, args):
 unary_math_extern(math.sinh, "sinhf", "sinh")
 unary_math_extern(math.cosh, "coshf", "cosh")
 unary_math_extern(math.tanh, "tanhf", "tanh")
-unary_math_extern(math.ceil, "ceilf", "ceil")
-unary_math_extern(math.floor, "floorf", "floor")
+# math.floor and math.ceil return float on 2.x, int on 3.x
+if utils.PYVERSION > (3, 0):
+    unary_math_extern(math.ceil, "ceilf", "ceil", types.int64)
+    unary_math_extern(math.floor, "floorf", "floor", types.int64)
+else:
+    unary_math_extern(math.ceil, "ceilf", "ceil")
+    unary_math_extern(math.floor, "floorf", "floor")
 unary_math_extern(math.sqrt, "sqrtf", "sqrt")
-unary_math_extern(math.trunc, "truncf", "trunc")
+unary_math_extern(math.trunc, "truncf", "trunc", types.int64)
 
 
 @register
diff --git a/numba/typing/mathdecl.py b/numba/typing/mathdecl.py
--- a/numba/typing/mathdecl.py
+++ b/numba/typing/mathdecl.py
@@ -115,6 +115,16 @@ class Math_unary(ConcreteTemplate):
     ]
 
 
+class Math_converter(ConcreteTemplate):
+    cases = [
+        signature(types.intp, types.intp),
+        signature(types.int64, types.int64),
+        signature(types.uint64, types.uint64),
+        signature(types.int64, types.float32),
+        signature(types.int64, types.float64),
+    ]
+
+
 class Math_fabs(Math_unary):
     key = math.fabs
 
@@ -202,15 +212,29 @@ class Math_atanh(Math_unary):
     key = math.atanh
 
 
-class Math_floor(Math_unary):
-    key = math.floor
+
+# math.floor and math.ceil return float on 2.x, int on 3.x
+if utils.PYVERSION > (3, 0):
+
+    class Math_floor(Math_converter):
+        key = math.floor
+
+
+    class Math_ceil(Math_converter):
+        key = math.ceil
+
+
+else:
+
+    class Math_floor(Math_unary):
+        key = math.floor
 
 
-class Math_ceil(Math_unary):
-    key = math.ceil
+    class Math_ceil(Math_unary):
+        key = math.ceil
 
 
-class Math_trunc(Math_unary):
+class Math_trunc(Math_converter):
     key = math.trunc
 
 
diff --git a/numba/typing/templates.py b/numba/typing/templates.py
--- a/numba/typing/templates.py
+++ b/numba/typing/templates.py
@@ -108,7 +108,10 @@ def resolve_overload(context, key, cases, args, kws):
         if len(ordered) > 1:
             (first, case1), (second, case2) = ordered[:2]
             # Ambiguous overloading
-            if first == second:
+            # NOTE: we can have duplicate overloadings if e.g. some type
+            # aliases were used when declaring the supported signatures
+            # (typical example being "intp" and "int64" on a 64-bit build)
+            if first == second and case1 != case2:
                 ambiguous = []
                 for rate, case in ordered:
                     if rate == first:


Test patch:
diff --git a/numba/tests/test_mathlib.py b/numba/tests/test_mathlib.py
--- a/numba/tests/test_mathlib.py
+++ b/numba/tests/test_mathlib.py
@@ -8,6 +8,9 @@
 
 PY27_AND_ABOVE = utils.PYVERSION > (2, 6)
 
+INT_TYPES = (int,)
+if utils.PYVERSION < (3, 0):
+    INT_TYPES += (long,)
 
 
 enable_pyobj_flags = Flags()
@@ -143,9 +146,11 @@ def gamma(x):
 def lgamma(x):
     return math.lgamma(x)
 
+
 def pow(x, y):
     return math.pow(x, y)
 
+
 class TestMathLib(TestCase):
 
     def run_unary(self, pyfunc, x_types, x_values, flags=enable_pyobj_flags,
@@ -153,7 +158,13 @@ def run_unary(self, pyfunc, x_types, x_values, flags=enable_pyobj_flags,
         for tx, vx in zip(x_types, x_values):
             cr = compile_isolated(pyfunc, [tx], flags=flags)
             cfunc = cr.entry_point
-            self.assertAlmostEqual(cfunc(vx), pyfunc(vx), places=places)
+            got = cfunc(vx)
+            expected = pyfunc(vx)
+            self.assertAlmostEqual(got, expected, places=places)
+            if isinstance(expected, float):
+                self.assertIsInstance(got, float)
+            else:
+                self.assertIsInstance(got, INT_TYPES)
 
     def test_sin(self, flags=enable_pyobj_flags):
         pyfunc = sin