From 68e15a2bd88b4fc55f92647ef3cdeff455a71f9d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 29 Apr 2019 14:46:04 +0900 Subject: [PATCH 1/4] Add basic float.__round__ without ndigits support --- tests/snippets/math_basics.py | 7 +++++++ vm/src/obj/objfloat.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 16d6e011c..a9092d58d 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -18,3 +18,10 @@ assert 2 == a / 2 assert a - 3 == 1 assert -a == -4 assert +a == 4 + +assert round(1.2) == 1 +assert round(1.8) == 2 +assert round(0.5) == 0 +assert round(1.5) == 2 +assert round(-0.5) == 0 +assert round(-1.5) == -2 diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index a588eee6a..f40c403a8 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -2,9 +2,11 @@ use super::objbytes; use super::objint; use super::objstr; use super::objtype; +use crate::function::OptionalArg; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::{BigInt, ToBigInt}; @@ -357,6 +359,36 @@ impl PyFloat { self.value.to_bigint().unwrap() } + #[pymethod(name = "__round__")] + fn round(&self, ndigits: OptionalArg, vm: &VirtualMachine) -> PyObjectRef { + let ndigits = match ndigits { + OptionalArg::Missing => None, + OptionalArg::Present(ref value) => { + if !vm.get_none().is(value) { + // retrive int to implement it + Some(vm.ctx.not_implemented()) + } else { + None + } + } + }; + if ndigits.is_none() { + let fract = self.value.fract(); + let value = if fract.abs() == 0.5 { + if self.value.trunc() % 2.0 == 0.0 { + self.value - fract + } else { + self.value + fract + } + } else { + self.value.round() + }; + vm.ctx.new_int(value.to_bigint().unwrap()) + } else { + vm.ctx.not_implemented() + } + } + #[pymethod(name = "__int__")] fn int(&self, vm: &VirtualMachine) -> BigInt { self.trunc(vm) From 4c8f655d4ee6d0aef5ae65334a9cdc2666d9a04e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 01:37:47 +0900 Subject: [PATCH 2/4] float.__round__ inf/nan support --- tests/snippets/math_basics.py | 15 ++++++++++++++- vm/src/obj/objfloat.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index a9092d58d..51ea5c726 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -1,4 +1,4 @@ -from testutils import assertRaises +from testutils import assert_raises a = 4 @@ -25,3 +25,16 @@ assert round(0.5) == 0 assert round(1.5) == 2 assert round(-0.5) == 0 assert round(-1.5) == -2 + +assert_raises( + ValueError, + lambda: round(float('nan')), + 'ValueError: cannot convert float NaN to integer') +assert_raises( + OverflowError, + lambda: round(float('inf')), + 'OverflowError: cannot convert float NaN to integer') +assert_raises( + OverflowError, + lambda: round(-float('inf')), + 'OverflowError: cannot convert float NaN to integer') diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index f40c403a8..bb7a65975 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -51,6 +51,28 @@ fn mod_(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { } } +fn try_to_bigint(value: f64, vm: &VirtualMachine) -> PyResult { + match value.to_bigint() { + Some(int) => Ok(int), + None => { + if value.is_infinite() { + Err(vm.new_overflow_error( + "OverflowError: cannot convert float NaN to integer".to_string(), + )) + } else if value.is_nan() { + Err(vm + .new_value_error("ValueError: cannot convert float NaN to integer".to_string())) + } else { + // unreachable unless BigInt has a bug + unreachable!( + "A finite float value failed to be converted to bigint: {}", + value + ) + } + } + } +} + #[pyimpl] impl PyFloat { #[pymethod(name = "__eq__")] @@ -360,7 +382,7 @@ impl PyFloat { } #[pymethod(name = "__round__")] - fn round(&self, ndigits: OptionalArg, vm: &VirtualMachine) -> PyObjectRef { + fn round(&self, ndigits: OptionalArg, vm: &VirtualMachine) -> PyResult { let ndigits = match ndigits { OptionalArg::Missing => None, OptionalArg::Present(ref value) => { @@ -383,9 +405,10 @@ impl PyFloat { } else { self.value.round() }; - vm.ctx.new_int(value.to_bigint().unwrap()) + let int = try_to_bigint(value, vm)?; + Ok(vm.ctx.new_int(int)) } else { - vm.ctx.not_implemented() + Ok(vm.ctx.not_implemented()) } } From b7c7591639617e845d0d7e8c141887a1e2bf91e2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 01:48:03 +0900 Subject: [PATCH 3/4] Fix float.__trunc__ to handle abnormal values --- tests/snippets/floats.py | 4 +++- vm/src/obj/objfloat.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index c4f7bdb64..8820813f1 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -111,7 +111,9 @@ assert 1.2.__float__() == 1.2 assert 1.2.__trunc__() == 1 assert int(1.2) == 1 assert float(1.2) == 1.2 -# assert math.trunc(1.2) == 1 +assert math.trunc(1.2) == 1 +assert_raises(OverflowError, float('inf').__trunc__) +assert_raises(ValueError, float('nan').__trunc__) assert (1.7).real == 1.7 assert (1.3).is_integer() == False diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index bb7a65975..80c325a33 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -377,8 +377,8 @@ impl PyFloat { } #[pymethod(name = "__trunc__")] - fn trunc(&self, _vm: &VirtualMachine) -> BigInt { - self.value.to_bigint().unwrap() + fn trunc(&self, vm: &VirtualMachine) -> PyResult { + try_to_bigint(self.value, vm) } #[pymethod(name = "__round__")] @@ -413,7 +413,7 @@ impl PyFloat { } #[pymethod(name = "__int__")] - fn int(&self, vm: &VirtualMachine) -> BigInt { + fn int(&self, vm: &VirtualMachine) -> PyResult { self.trunc(vm) } From c951cb3cfd693536968dee2c675862b01761210e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 03:27:06 +0900 Subject: [PATCH 4/4] float.__round__ for ndigits == 0 --- tests/snippets/floats.py | 8 ++++++++ vm/src/obj/objfloat.rs | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 8820813f1..176ce927e 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -114,6 +114,14 @@ 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 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_raises(OverflowError, float('inf').__round__) +assert_raises(ValueError, float('nan').__round__) assert (1.7).real == 1.7 assert (1.3).is_integer() == False diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 80c325a33..42810da8e 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -11,7 +11,7 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; -use num_traits::ToPrimitive; +use num_traits::{ToPrimitive, Zero}; #[pyclass(name = "float")] #[derive(Debug, Copy, Clone, PartialEq)] @@ -387,8 +387,19 @@ impl PyFloat { OptionalArg::Missing => None, OptionalArg::Present(ref value) => { if !vm.get_none().is(value) { - // retrive int to implement it - Some(vm.ctx.not_implemented()) + let ndigits = if objtype::isinstance(value, &vm.ctx.int_type()) { + objint::get_value(value) + } else { + return Err(vm.new_type_error(format!( + "TypeError: '{}' object cannot be interpreted as an integer", + value.class().name + ))); + }; + if ndigits.is_zero() { + None + } else { + Some(ndigits) + } } else { None }