fix __future__ division semantics for constant expressions and C integers
authorStefan Behnel <scoder@users.berlios.de>
Wed, 8 Jul 2009 19:13:14 +0000 (21:13 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Wed, 8 Jul 2009 19:13:14 +0000 (21:13 +0200)
--HG--
rename : tests/run/future_division.pyx => tests/run/non_future_division.pyx

Cython/Compiler/ExprNodes.py
Cython/Compiler/Nodes.py
Cython/Compiler/Parsing.py
tests/run/future_division.pyx
tests/run/non_future_division.pyx [new file with mode: 0644]

index 37becf48edeb225b76cd2ad14839eb3e04358273..4f2f29818059682aa13e79adc08e3151ab8803f9 100644 (file)
@@ -728,9 +728,7 @@ class FloatNode(ConstNode):
     type = PyrexTypes.c_double_type
 
     def calculate_constant_result(self):
-        # calculating float values is usually not a good idea
-        #self.constant_result = float(self.value)
-        pass
+        self.constant_result = float(self.value)
 
     def compile_time_value(self, denv):
         return float(self.value)
@@ -4111,7 +4109,7 @@ compile_time_binary_operators = {
     'is_not': operator.is_not,
     '+': operator.add,
     '&': operator.and_,
-    '/': operator.div,
+    '/': operator.truediv,
     '//': operator.floordiv,
     '<<': operator.lshift,
     '%': operator.mod,
@@ -4120,7 +4118,6 @@ compile_time_binary_operators = {
     '**': operator.pow,
     '>>': operator.rshift,
     '-': operator.sub,
-    #'/': operator.truediv,
     '^': operator.xor,
     'in': operator.contains,
     'not_in': _not_in,
@@ -4278,13 +4275,13 @@ class NumBinopNode(BinopNode):
         "|":        "PyNumber_Or",
         "^":        "PyNumber_Xor",
         "&":        "PyNumber_And",
-        "<<":        "PyNumber_Lshift",
-        ">>":        "PyNumber_Rshift",
+        "<<":       "PyNumber_Lshift",
+        ">>":       "PyNumber_Rshift",
         "+":        "PyNumber_Add",
         "-":        "PyNumber_Subtract",
         "*":        "PyNumber_Multiply",
         "/":        "__Pyx_PyNumber_Divide",
-        "//":        "PyNumber_FloorDivide",
+        "//":       "PyNumber_FloorDivide",
         "%":        "PyNumber_Remainder",
         "**":       "PyNumber_Power"
     }
@@ -4350,20 +4347,63 @@ class DivNode(NumBinopNode):
     #  '/' or '//' operator.
     
     cdivision = None
+    truedivision = None   # == "unknown" if operator == '/'
+    ctruedivision = False
     cdivision_warnings = False
     zerodivision_check = None
-    
+
+    def find_compile_time_binary_operator(self, op1, op2):
+        func = compile_time_binary_operators[self.operator]
+        if self.operator == '/' and self.truedivision is None:
+            # => true div for floats, floor div for integers
+            if isinstance(op1, (int,long)) and isinstance(op2, (int,long)):
+                func = compile_time_binary_operators['//']
+        return func
+
+    def calculate_constant_result(self):
+        op1 = self.operand1.constant_result
+        op2 = self.operand2.constant_result
+        func = self.find_compile_time_binary_operator(op1, op2)
+        self.constant_result = func(
+            self.operand1.constant_result,
+            self.operand2.constant_result)
+
+    def compile_time_value(self, denv):
+        operand1 = self.operand1.compile_time_value(denv)
+        operand2 = self.operand2.compile_time_value(denv)
+        try:
+            func = self.find_compile_time_binary_operator(
+                self, operand1, operand2)
+            return func(operand1, operand2)
+        except Exception, e:
+            self.compile_time_value_error(e)
+
     def analyse_types(self, env):
+        if self.cdivision or env.directives['cdivision']:
+            self.ctruedivision = False
+        else:
+            self.ctruedivision = self.truedivision
         NumBinopNode.analyse_types(self, env)
         if not self.type.is_pyobject:
-            self.zerodivision_check = self.cdivision is None and not env.directives['cdivision']
+            self.zerodivision_check = (
+                self.cdivision is None and not env.directives['cdivision']
+                and (self.operand2.constant_result is not_a_constant or
+                     self.operand2.constant_result == 0))
             if self.zerodivision_check or env.directives['cdivision_warnings']:
                 # Need to check ahead of time to warn or raise zero division error
                 self.operand1 = self.operand1.coerce_to_simple(env)
                 self.operand2 = self.operand2.coerce_to_simple(env)
                 if env.nogil:
                     error(self.pos, "Pythonic division not allowed without gil, consider using cython.cdivision(True)")
-    
+
+    def compute_c_result_type(self, type1, type2):
+        if self.operator == '/' and self.ctruedivision:
+            if not type1.is_float and not type2.is_float:
+                widest_type = PyrexTypes.widest_numeric_type(type1, PyrexTypes.c_double_type)
+                widest_type = PyrexTypes.widest_numeric_type(type2, widest_type)
+                return widest_type
+        return NumBinopNode.compute_c_result_type(self, type1, type2)
+
     def zero_division_message(self):
         if self.type.is_int:
             return "integer division or modulo by zero"
@@ -4416,12 +4456,17 @@ class DivNode(NumBinopNode):
             return NumBinopNode.calculate_result_code(self)
         elif self.type.is_float and self.operator == '//':
             return "floor(%s / %s)" % (
-                self.operand1.result(), 
-                self.operand2.result())
-        elif self.cdivision:
-            return "(%s / %s)" % (
-                self.operand1.result(), 
+                self.operand1.result(),
                 self.operand2.result())
+        elif self.truedivision or self.cdivision:
+            op1 = self.operand1.result()
+            op2 = self.operand2.result()
+            if self.truedivision:
+                if self.type != self.operand1.type:
+                    op1 = self.type.cast_code(op1)
+                if self.type != self.operand2.type:
+                    op2 = self.type.cast_code(op2)
+            return "(%s / %s)" % (op1, op2)
         else:
             return "__Pyx_div_%s(%s, %s)" % (
                     self.type.specalization_name(),
index 17aa3f81075a7e652a1c17220a839789421ec205..2ef1f9b04e175074675600a0424f8aeda00abd51 100644 (file)
@@ -734,7 +734,7 @@ class CBufferAccessTypeNode(CBaseTypeNode):
             self.positional_args,
             self.keyword_args,
             base_type.buffer_defaults)
-        
+
         self.type = PyrexTypes.BufferType(base_type, **options)
         return self.type
 
index 724e4c1b3a1ce43a6b1d778ef8387fdd15eec096..885b8518d00eeca4bbe2ae59d128025c74bb12c3 100644 (file)
@@ -65,14 +65,23 @@ def p_ident_list(s):
 #
 #------------------------------------------
 
+def p_binop_operator(s):
+    pos = s.position()
+    op = s.sy
+    s.next()
+    return op, pos
+
 def p_binop_expr(s, ops, p_sub_expr):
     n1 = p_sub_expr(s)
     while s.sy in ops:
-        op = s.sy
-        pos = s.position()
-        s.next()
+        op, pos = p_binop_operator(s)
         n2 = p_sub_expr(s)
         n1 = ExprNodes.binop_node(pos, op, n1, n2)
+        if op == '/':
+            if Future.division in s.context.future_directives:
+                n1.truedivision = True
+            else:
+                n1.truedivision = None # unknown
     return n1
 
 #expression: or_test [if or_test else test] | lambda_form
index cfb948f978fa1777396292a7633eadc07c6f73bf..f87b83a84c8382e6218e4c27cc65719b98642f52 100644 (file)
@@ -1,7 +1,6 @@
 from __future__ import division
 
 __doc__ = u"""
->>> from future_division import doit
 >>> doit(1,2)
 (0.5, 0)
 >>> doit(4,3)
@@ -10,7 +9,55 @@ __doc__ = u"""
 (1.3333333333333333, 1.0)
 >>> doit(4,2)
 (2.0, 2)
+
+>>> constants()
+(0.5, 0, 2.5, 2.0, 2.5, 2)
+
+>>> py_mix(1)
+(0.5, 0, 0.5, 0.0, 0.5, 0)
+
+>>> py_mix_rev(4)
+(0.25, 0, 1.25, 1.0, 1.25, 1)
+
+>>> py_mix(1.0)
+(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)
+
+>>> py_mix_rev(4.0)
+(0.25, 0.0, 1.25, 1.0, 1.25, 1.0)
+
+>>> int_mix(1)
+(0.5, 0, 0.5, 0.0, 0.5, 0)
+
+>>> int_mix_rev(4)
+(0.25, 0, 1.25, 1.0, 1.25, 1)
+
+>>> float_mix(1.0)
+(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)
+
+>>> float_mix_rev(4.0)
+(0.25, 0.0, 1.25, 1.0, 1.25, 1.0)
 """
 
 def doit(x,y):
     return x/y, x//y
+
+def constants():
+    return 1/2, 1//2, 5/2.0, 5//2.0, 5/2, 5//2
+
+def py_mix(a):
+    return a/2, a//2, a/2.0, a//2.0, a/2, a//2
+
+def py_mix_rev(a):
+    return 1/a, 1//a, 5.0/a, 5.0//a, 5/a, 5//a
+
+def int_mix(int a):
+    return a/2, a//2, a/2.0, a//2.0, a/2, a//2
+
+def int_mix_rev(int a):
+    return 1/a, 1//a, 5.0/a, 5.0//a, 5/a, 5//a
+
+def float_mix(float a):
+    return a/2, a//2, a/2.0, a//2.0, a/2, a//2
+
+def float_mix_rev(float a):
+    return 1/a, 1//a, 5.0/a, 5.0//a, 5/a, 5//a
diff --git a/tests/run/non_future_division.pyx b/tests/run/non_future_division.pyx
new file mode 100644 (file)
index 0000000..61acb1b
--- /dev/null
@@ -0,0 +1,63 @@
+# Py2.x mixed true-div/floor-div behaviour of '/' operator
+
+__doc__ = u"""
+>>> doit(1,2)
+(0, 0)
+>>> doit(4,3)
+(1, 1)
+>>> doit(4,3.0)
+(1.3333333333333333, 1.0)
+>>> doit(4,2)
+(2, 2)
+
+>>> constants()
+(0, 0, 2.5, 2.0, 2, 2)
+
+>>> py_mix(1)
+(0, 0, 0.5, 0.0, 0, 0)
+
+>>> py_mix_rev(4)
+(0, 0, 1.25, 1.0, 1, 1)
+
+>>> py_mix(1.0)
+(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)
+
+>>> py_mix_rev(4.0)
+(0.25, 0.0, 1.25, 1.0, 1.25, 1.0)
+
+>>> int_mix(1)
+(0, 0, 0.5, 0.0, 0, 0)
+
+>>> int_mix_rev(4)
+(0, 0, 1.25, 1.0, 1, 1)
+
+>>> float_mix(1.0)
+(0.5, 0.0, 0.5, 0.0, 0.5, 0.0)
+
+>>> float_mix_rev(4.0)
+(0.25, 0.0, 1.25, 1.0, 1.25, 1.0)
+"""
+
+def doit(x,y):
+    return x/y, x//y
+
+def constants():
+    return 1/2, 1//2, 5/2.0, 5//2.0, 5/2, 5//2
+
+def py_mix(a):
+    return a/2, a//2, a/2.0, a//2.0, a/2, a//2
+
+def py_mix_rev(a):
+    return 1/a, 1//a, 5.0/a, 5.0//a, 5/a, 5//a
+
+def int_mix(int a):
+    return a/2, a//2, a/2.0, a//2.0, a/2, a//2
+
+def int_mix_rev(int a):
+    return 1/a, 1//a, 5.0/a, 5.0//a, 5/a, 5//a
+
+def float_mix(float a):
+    return a/2, a//2, a/2.0, a//2.0, a/2, a//2
+
+def float_mix_rev(float a):
+    return 1/a, 1//a, 5.0/a, 5.0//a, 5/a, 5//a