From: Stefan Behnel Date: Wed, 8 Jul 2009 19:13:14 +0000 (+0200) Subject: fix __future__ division semantics for constant expressions and C integers X-Git-Tag: 0.12.alpha0~253 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=3d2aa773bd79e1e5751de2ea2148696f52580f42;p=cython.git fix __future__ division semantics for constant expressions and C integers --HG-- rename : tests/run/future_division.pyx => tests/run/non_future_division.pyx --- diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 37becf48..4f2f2981 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -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(), diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 17aa3f81..2ef1f9b0 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -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 diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 724e4c1b..885b8518 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -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 diff --git a/tests/run/future_division.pyx b/tests/run/future_division.pyx index cfb948f9..f87b83a8 100644 --- a/tests/run/future_division.pyx +++ b/tests/run/future_division.pyx @@ -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 index 00000000..61acb1bc --- /dev/null +++ b/tests/run/non_future_division.pyx @@ -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