diff --git a/tests/snippets/builtin_round.py b/tests/snippets/builtin_round.py new file mode 100644 index 000000000..6d116b307 --- /dev/null +++ b/tests/snippets/builtin_round.py @@ -0,0 +1,21 @@ +from testutils import assertRaises + +assert round(0) == 0 +assert isinstance(round(0), int) +assert round(0.0) == 0 +assert isinstance(round(0.0), int) + +assert round(0, None) == 0 +assert isinstance(round(0, None), int) +assert round(0.0, None) == 0 +assert isinstance(round(0, None), int) + +assert round(0, 0) == 0 +assert isinstance(round(0, 0), int) +assert round(0.0, 0) == 0.0 # Cannot check the type +assert isinstance(round(0.0, 0), float) + +with assertRaises(TypeError): + round(0, 0.0) +with assertRaises(TypeError): + round(0.0, 0.0) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 0200e5a36..6139295f4 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -151,12 +151,20 @@ assert float(1.2) == 1.2 assert math.trunc(1.2) == 1 assert_raises(OverflowError, float('inf').__trunc__) assert_raises(ValueError, float('nan').__trunc__) -assert 0.5.__round__() == 0.0 -assert 1.5.__round__() == 2.0 +assert isinstance(0.5.__round__(), int) +assert isinstance(1.5.__round__(), int) +assert 0.5.__round__() == 0 +assert 1.5.__round__() == 2 +assert isinstance(0.5.__round__(0), float) +assert isinstance(1.5.__round__(0), float) assert 0.5.__round__(0) == 0.0 assert 1.5.__round__(0) == 2.0 -assert 0.5.__round__(None) == 0.0 -assert 1.5.__round__(None) == 2.0 +assert isinstance(0.5.__round__(None), int) +assert isinstance(1.5.__round__(None), int) +assert 0.5.__round__(None) == 0 +assert 1.5.__round__(None) == 2 +assert_raises(TypeError, lambda: 0.5.__round__(0.0)) +assert_raises(TypeError, lambda: 1.5.__round__(0.0)) assert_raises(OverflowError, float('inf').__round__) assert_raises(ValueError, float('nan').__round__) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index d05848361..0e89d44f3 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -167,3 +167,16 @@ class F(float): return 3 assert int(F(1.2)) == 3 + +assert isinstance((0).__round__(), int) +assert isinstance((1).__round__(), int) +assert (0).__round__() == 0 +assert (1).__round__() == 1 +assert isinstance((0).__round__(0), int) +assert isinstance((1).__round__(0), int) +assert (0).__round__(0) == 0 +assert (1).__round__(0) == 1 +assert_raises(TypeError, lambda: (0).__round__(None)) +assert_raises(TypeError, lambda: (1).__round__(None)) +assert_raises(TypeError, lambda: (0).__round__(0.0)) +assert_raises(TypeError, lambda: (1).__round__(0.0)) \ No newline at end of file diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index b36d2edf3..33ad00044 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -734,9 +734,19 @@ fn builtin_round(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(ndigits, None)] ); if let Some(ndigits) = ndigits { - let ndigits = vm.call_method(ndigits, "__int__", vec![])?; - let rounded = vm.call_method(number, "__round__", vec![ndigits])?; - Ok(rounded) + if objtype::isinstance(ndigits, &vm.ctx.int_type()) { + let ndigits = vm.call_method(ndigits, "__int__", vec![])?; + let rounded = vm.call_method(number, "__round__", vec![ndigits])?; + Ok(rounded) + } else if vm.ctx.none().is(ndigits) { + let rounded = &vm.call_method(number, "__round__", vec![])?; + Ok(vm.ctx.new_int(objint::get_value(rounded).clone())) + } else { + Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + ndigits.class().name + ))) + } } else { // without a parameter, the result type is coerced to int let rounded = &vm.call_method(number, "__round__", vec![])?; diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 50bcf6a5f..b0dbaebe2 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -440,25 +440,38 @@ impl PyFloat { OptionalArg::Missing => None, OptionalArg::Present(ref value) => { if !vm.get_none().is(value) { - let ndigits = if objtype::isinstance(value, &vm.ctx.int_type()) { - objint::get_value(value) - } else { + if !objtype::isinstance(value, &vm.ctx.int_type()) { return Err(vm.new_type_error(format!( - "TypeError: '{}' object cannot be interpreted as an integer", + "'{}' object cannot be interpreted as an integer", value.class().name ))); }; - if ndigits.is_zero() { - None - } else { - Some(ndigits) - } + // Only accept int type ndigits + let ndigits = objint::get_value(value); + Some(ndigits) } else { None } } }; - if ndigits.is_none() { + + if let Some(ndigits) = ndigits { + if ndigits.is_zero() { + let fract = self.value.fract(); + let value = if (fract.abs() - 0.5).abs() < std::f64::EPSILON { + if self.value.trunc() % 2.0 == 0.0 { + self.value - fract + } else { + self.value + fract + } + } else { + self.value.round() + }; + Ok(vm.ctx.new_float(value)) + } else { + Ok(vm.ctx.not_implemented()) + } + } else { let fract = self.value.fract(); let value = if (fract.abs() - 0.5).abs() < std::f64::EPSILON { if self.value.trunc() % 2.0 == 0.0 { @@ -471,8 +484,6 @@ impl PyFloat { }; let int = try_to_bigint(value, vm)?; Ok(vm.ctx.new_int(int)) - } else { - Ok(vm.ctx.not_implemented()) } } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index d13b9b6fa..41fb1a9bd 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -9,13 +9,14 @@ use crate::function::{KwArgs, OptionalArg, PyFuncArgs}; use crate::obj::objtype::PyClassRef; use crate::pyhash; use crate::pyobject::{ - IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; use super::objbyteinner::PyByteInner; use super::objbytes::PyBytes; +use super::objint; use super::objstr::{PyString, PyStringRef}; use super::objtype; @@ -468,8 +469,29 @@ impl PyInt { zelf: PyRef, _precision: OptionalArg, _vm: &VirtualMachine, - ) -> PyIntRef { - zelf + ) -> PyResult { + let _ndigits = match _precision { + OptionalArg::Missing => None, + OptionalArg::Present(ref value) => { + if !_vm.get_none().is(value) { + if !objtype::isinstance(value, &_vm.ctx.int_type()) { + return Err(_vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + value.class().name + ))); + }; + // Only accept int type _ndigits + let _ndigits = objint::get_value(value); + Some(_ndigits) + } else { + return Err(_vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + value.class().name + ))); + } + } + }; + Ok(zelf) } #[pymethod(name = "__int__")]