From c138dea9926fe5fe4148e33485ffb14bb02c7e08 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 04:03:18 +0900 Subject: [PATCH 1/6] Remove int.__pow__ float handling --- vm/src/obj/objint.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 3687ed2b0..98fc39a1b 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -13,7 +13,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objfloat::{self, PyFloat}; +use super::objfloat::PyFloat; use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -327,9 +327,6 @@ impl PyInt { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other).to_u32().unwrap(); vm.ctx.new_int(self.value.pow(v2)) - } else if objtype::isinstance(&other, &vm.ctx.float_type()) { - let v2 = objfloat::get_value(&other); - vm.ctx.new_float((self.value.to_f64().unwrap()).powf(v2)) } else { vm.ctx.not_implemented() } From 614378d7202821c070563ac5a773ecd39ab036d9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 04:56:51 +0900 Subject: [PATCH 2/6] Add int.__pow__ negative exp impl --- tests/snippets/ints.py | 3 +++ vm/src/obj/objint.rs | 25 +++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index a072cc542..3aed1144d 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -37,6 +37,8 @@ assert (2).__mul__(1) == 2 assert (2).__rmul__(1) == 2 assert (2).__truediv__(1) == 2.0 assert (2).__rtruediv__(1) == 0.5 +assert (2).__pow__(3) == 8 +assert (10).__pow__(-1) == 0.1 # real/imag attributes assert (1).real == 1 @@ -58,6 +60,7 @@ assert (2).__mul__(1.0) == NotImplemented assert (2).__rmul__(1.0) == NotImplemented assert (2).__truediv__(1.0) == NotImplemented assert (2).__rtruediv__(1.0) == NotImplemented +assert (2).__pow__(3.0) == NotImplemented assert int() == 0 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 98fc39a1b..12caa060a 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -323,13 +323,21 @@ impl PyInt { } #[pymethod(name = "__pow__")] - fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if objtype::isinstance(&other, &vm.ctx.int_type()) { - let v2 = get_value(&other).to_u32().unwrap(); - vm.ctx.new_int(self.value.pow(v2)) + fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(if objtype::isinstance(&other, &vm.ctx.int_type()) { + let other = other.payload::().unwrap(); + if other.value.is_negative() { + let v1 = self.float(vm)?; + let v2 = other.float(vm)?; + vm.ctx.new_float(v1.pow(v2.to_f64().unwrap())) + } else if let Some(v2) = other.value.to_u32() { + vm.ctx.new_int(self.value.pow(v2)) + } else { + vm.ctx.not_implemented() + } } else { vm.ctx.not_implemented() - } + }) } #[pymethod(name = "__mod__")] @@ -400,10 +408,9 @@ impl PyInt { } #[pymethod(name = "__float__")] - fn float(&self, vm: &VirtualMachine) -> PyResult { + fn float(&self, vm: &VirtualMachine) -> PyResult { self.value .to_f64() - .map(PyFloat::from) .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string())) } @@ -541,9 +548,7 @@ pub fn get_value(obj: &PyObjectRef) -> &BigInt { } pub fn get_float_value(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - get_value(obj).to_f64().ok_or_else(|| { - vm.new_overflow_error("OverflowError: int too large to convert to float".to_string()) - }) + obj.payload::().unwrap().float(vm) } #[inline] From eb9ca06ee721eebd991cde1b7d42bdf49837819d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 05:09:11 +0900 Subject: [PATCH 3/6] Add int.__rpow__ --- tests/snippets/ints.py | 2 ++ vm/src/obj/objint.rs | 39 +++++++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 3aed1144d..40b4c2c6d 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -39,6 +39,7 @@ assert (2).__truediv__(1) == 2.0 assert (2).__rtruediv__(1) == 0.5 assert (2).__pow__(3) == 8 assert (10).__pow__(-1) == 0.1 +assert (2).__rpow__(3) == 9 # real/imag attributes assert (1).real == 1 @@ -61,6 +62,7 @@ assert (2).__rmul__(1.0) == NotImplemented assert (2).__truediv__(1.0) == NotImplemented assert (2).__rtruediv__(1.0) == NotImplemented assert (2).__pow__(3.0) == NotImplemented +assert (2).__rpow__(3.0) == NotImplemented assert int() == 0 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 12caa060a..fe977ca0b 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -111,6 +111,19 @@ impl_try_from_object_int!( (u64, to_u64), ); +fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { + Ok(if int2.value.is_negative() { + let v1 = int1.float(vm)?; + let v2 = int2.float(vm)?; + vm.ctx.new_float(v1.pow(v2)) + } else if let Some(v2) = int2.value.to_u64() { + vm.ctx.new_int(int1.value.pow(v2)) + } else { + // missing feature: BigInt exp + vm.ctx.not_implemented() + }) +} + #[pyimpl] impl PyInt { #[pymethod(name = "__eq__")] @@ -324,20 +337,22 @@ impl PyInt { #[pymethod(name = "__pow__")] fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Ok(if objtype::isinstance(&other, &vm.ctx.int_type()) { + if objtype::isinstance(&other, &vm.ctx.int_type()) { let other = other.payload::().unwrap(); - if other.value.is_negative() { - let v1 = self.float(vm)?; - let v2 = other.float(vm)?; - vm.ctx.new_float(v1.pow(v2.to_f64().unwrap())) - } else if let Some(v2) = other.value.to_u32() { - vm.ctx.new_int(self.value.pow(v2)) - } else { - vm.ctx.not_implemented() - } + inner_pow(self, &other, vm) } else { - vm.ctx.not_implemented() - }) + Ok(vm.ctx.not_implemented()) + } + } + + #[pymethod(name = "__rpow__")] + fn rpow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let other = other.payload::().unwrap(); + inner_pow(&other, self, vm) + } else { + Ok(vm.ctx.not_implemented()) + } } #[pymethod(name = "__mod__")] From 8d4562b0bbb08179c5218f7bfcc48e8ec9a28e8c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 22:32:45 +0900 Subject: [PATCH 4/6] Fix __builtins__.pow to reflect __rpow__ --- tests/snippets/math_basics.py | 6 +++++- vm/src/builtins.rs | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 51ea5c726..28c145e3f 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -14,7 +14,7 @@ assert a ** 3 == 64 assert a * 3 == 12 assert a / 2 == 2 assert 2 == a / 2 -# assert a % 3 == 1 +assert a % 3 == 1 assert a - 3 == 1 assert -a == -4 assert +a == 4 @@ -38,3 +38,7 @@ assert_raises( OverflowError, lambda: round(-float('inf')), 'OverflowError: cannot convert float NaN to integer') + +assert pow(2, 2) == 4 +assert pow(1, 2.0) == 1.0 +assert pow(2.0, 1) == 2.0 diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 300d11b31..27a53dc92 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -529,11 +529,11 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(x, None), (y, None)], optional = [(mod_value, Some(vm.ctx.int_type()))] ); - let pow_method_name = "__pow__"; - let result = match vm.get_method(x.clone(), pow_method_name) { - Ok(attrib) => vm.invoke(attrib, vec![y.clone()]), - Err(..) => Err(vm.new_type_error("unsupported operand type(s) for pow".to_string())), - }; + + let result = vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { + Err(vm.new_unsupported_operand_error(x, y, "pow")) + }); + //Check if the 3rd argument is defined and perform modulus on the result //this should be optimized in the future to perform a "power-mod" algorithm in //order to improve performance From c18615b6e5d5011a2330790d169dcbffede06369 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 23:34:11 +0900 Subject: [PATCH 5/6] Fix __builtins__.pow to perform modpow for 3rd argument --- tests/snippets/math_basics.py | 22 +++++++++++++++++++ vm/src/builtins.rs | 41 +++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 28c145e3f..2cef18b74 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -42,3 +42,25 @@ assert_raises( assert pow(2, 2) == 4 assert pow(1, 2.0) == 1.0 assert pow(2.0, 1) == 2.0 + +assert pow(2, 4, 5) == 1 +assert_raises( + TypeError, + lambda: pow(2, 4, 5.0), + 'pow() 3rd argument not allowed unless all arguments are integers') +assert_raises( + TypeError, + lambda: pow(2, 4.0, 5), + 'pow() 3rd argument not allowed unless all arguments are integers') +assert_raises( + TypeError, + lambda: pow(2.0, 4, 5), + 'pow() 3rd argument not allowed unless all arguments are integers') +assert_raises( + ValueError, + lambda: pow(2, -1, 5), + 'pow() 2nd argument cannot be negative when 3rd argument specified') +assert_raises( + ValueError, + lambda: pow(2, 2, 0), + 'pow() 3rd argument cannot be 0') diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 27a53dc92..79c276888 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -6,7 +6,8 @@ use std::char; use std::io::{self, Write}; use std::path::PathBuf; -use num_traits::Signed; +use num_bigint::Sign; +use num_traits::{Signed, Zero}; use crate::compile; use crate::import::import_module; @@ -530,24 +531,32 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mod_value, Some(vm.ctx.int_type()))] ); - let result = vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { - Err(vm.new_unsupported_operand_error(x, y, "pow")) - }); - - //Check if the 3rd argument is defined and perform modulus on the result - //this should be optimized in the future to perform a "power-mod" algorithm in - //order to improve performance match mod_value { - Some(mod_value) => { - let mod_method_name = "__mod__"; - match vm.get_method(result.expect("result not defined").clone(), mod_method_name) { - Ok(value) => vm.invoke(value, vec![mod_value.clone()]), - Err(..) => { - Err(vm.new_type_error("unsupported operand type(s) for mod".to_string())) - } + None => vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { + Err(vm.new_unsupported_operand_error(x, y, "pow")) + }), + Some(m) => { + // Check if the 3rd argument is defined and perform modulus on the result + if !(objtype::isinstance(x, &vm.ctx.int_type()) + && objtype::isinstance(y, &vm.ctx.int_type())) + { + return Err(vm.new_type_error( + "pow() 3rd argument not allowed unless all arguments are integers".to_string(), + )); } + let y = objint::get_value(y); + if y.sign() == Sign::Minus { + return Err(vm.new_value_error( + "pow() 2nd argument cannot be negative when 3rd argument specified".to_string(), + )); + } + let m = objint::get_value(m); + if m.is_zero() { + return Err(vm.new_value_error("pow() 3rd argument cannot be 0".to_string())); + } + let x = objint::get_value(x); + Ok(vm.new_int(x.modpow(&y, &m))) } - None => result, } } From 0bf7ff96b804eb454f6bab2800a2644c19a63124 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 2 May 2019 22:42:28 +0900 Subject: [PATCH 6/6] Special handling for base -1, 0, 1 for big exp --- tests/snippets/math_basics.py | 5 +++++ vm/src/obj/objint.rs | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 2cef18b74..d950b13a9 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -39,9 +39,14 @@ assert_raises( lambda: round(-float('inf')), 'OverflowError: cannot convert float NaN to integer') +assert pow(0, 0) == 1 assert pow(2, 2) == 4 assert pow(1, 2.0) == 1.0 assert pow(2.0, 1) == 2.0 +assert pow(0, 10**1000) == 0 +assert pow(1, 10**1000) == 1 +assert pow(-1, 10**1000+1) == -1 +assert pow(-1, 10**1000) == 1 assert pow(2, 4, 5) == 1 assert_raises( diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index fe977ca0b..88c91f89f 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::{Pow, Signed, ToPrimitive, Zero}; +use num_traits::{One, Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; use crate::function::{OptionalArg, PyFuncArgs}; @@ -13,7 +13,6 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objfloat::PyFloat; use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -116,11 +115,22 @@ fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { let v1 = int1.float(vm)?; let v2 = int2.float(vm)?; vm.ctx.new_float(v1.pow(v2)) - } else if let Some(v2) = int2.value.to_u64() { - vm.ctx.new_int(int1.value.pow(v2)) } else { - // missing feature: BigInt exp - vm.ctx.not_implemented() + if let Some(v2) = int2.value.to_u64() { + vm.ctx.new_int(int1.value.pow(v2)) + } else if int1.value.is_one() || int1.value.is_zero() { + vm.ctx.new_int(int1.value.clone()) + } else if int1.value == BigInt::from(-1) { + if int2.value.is_odd() { + vm.ctx.new_int(-1) + } else { + vm.ctx.new_int(1) + } + } else { + // missing feature: BigInt exp + // practically, exp over u64 is not possible to calculate anyway + vm.ctx.not_implemented() + } }) }