Skip to content

Commit bea6b48

Browse files
Merge pull request RustPython#885 from youknowone/pyfloat
Add __int__, __float__, __trunc__, __rmod__ to float
2 parents 88e52ef + 20b2f64 commit bea6b48

File tree

3 files changed

+116
-52
lines changed

3 files changed

+116
-52
lines changed

tests/snippets/floats.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,41 @@
77
a = 1.2
88
b = 1.3
99
c = 1.2
10+
z = 2
11+
12+
assert -a == -1.2
13+
1014
assert a < b
1115
assert not b < a
1216
assert a <= b
1317
assert a <= c
18+
assert a < z
1419

1520
assert b > a
1621
assert not a > b
1722
assert not a > c
1823
assert b >= a
1924
assert c >= a
2025
assert not a >= b
26+
assert z > a
2127

2228
assert a + b == 2.5
2329
assert a - c == 0
2430
assert a / c == 1
31+
assert a % c == 0
32+
assert a + z == 3.2
33+
assert z + a == 3.2
34+
assert a - z == -0.8
35+
assert z - a == 0.8
36+
assert a / z == 0.6
37+
assert 6 / a == 5.0
38+
assert 2.0 % z == 0.0
39+
assert z % 2.0 == 0.0
2540

2641
assert a < 5
2742
assert a <= 5
43+
assert a < 5.5
44+
assert a <= 5.5
2845
try:
2946
assert a < 'a'
3047
except TypeError:
@@ -83,6 +100,18 @@
83100
assert 1.0.__rtruediv__(2) == 2.0
84101
assert 2.0.__mul__(1) == 2.0
85102
assert 2.0.__rsub__(1) == -1.0
103+
assert 2.0.__mod__(2) == 0.0
104+
assert 2.0.__rmod__(2) == 0.0
105+
assert_raises(ZeroDivisionError, lambda: 2.0 / 0)
106+
assert_raises(ZeroDivisionError, lambda: 2.0 // 0)
107+
assert_raises(ZeroDivisionError, lambda: 2.0 % 0)
108+
109+
assert 1.2.__int__() == 1
110+
assert 1.2.__float__() == 1.2
111+
assert 1.2.__trunc__() == 1
112+
assert int(1.2) == 1
113+
assert float(1.2) == 1.2
114+
# assert math.trunc(1.2) == 1
86115

87116
assert (1.7).real == 1.7
88117
assert (1.3).is_integer() == False

vm/src/obj/objfloat.rs

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ use super::objstr;
44
use super::objtype;
55
use crate::obj::objtype::PyClassRef;
66
use crate::pyobject::{
7-
IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
7+
IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
88
};
99
use crate::vm::VirtualMachine;
10-
use num_bigint::ToBigInt;
10+
use num_bigint::{BigInt, ToBigInt};
1111
use num_rational::Ratio;
1212
use num_traits::ToPrimitive;
1313

14+
#[pyclass(name = "float")]
1415
#[derive(Debug, Copy, Clone, PartialEq)]
1516
pub struct PyFloat {
1617
value: f64,
@@ -40,7 +41,17 @@ impl From<f64> for PyFloat {
4041
}
4142
}
4243

44+
fn mod_(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult {
45+
if v2 != 0.0 {
46+
Ok(vm.ctx.new_float(v1 % v2))
47+
} else {
48+
Err(vm.new_zero_division_error("float mod by zero".to_string()))
49+
}
50+
}
51+
52+
#[pyimpl]
4353
impl PyFloat {
54+
#[pymethod(name = "__eq__")]
4455
fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
4556
let value = self.value;
4657
let result = if objtype::isinstance(&other, &vm.ctx.float_type()) {
@@ -60,6 +71,7 @@ impl PyFloat {
6071
vm.ctx.new_bool(result)
6172
}
6273

74+
#[pymethod(name = "__lt__")]
6375
fn lt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
6476
let v1 = self.value;
6577
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
@@ -72,6 +84,7 @@ impl PyFloat {
7284
}
7385
}
7486

87+
#[pymethod(name = "__le__")]
7588
fn le(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
7689
let v1 = self.value;
7790
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
@@ -84,6 +97,7 @@ impl PyFloat {
8497
}
8598
}
8699

100+
#[pymethod(name = "__gt__")]
87101
fn gt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
88102
let v1 = self.value;
89103
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
@@ -96,6 +110,7 @@ impl PyFloat {
96110
}
97111
}
98112

113+
#[pymethod(name = "__ge__")]
99114
fn ge(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
100115
let v1 = self.value;
101116
if objtype::isinstance(&i2, &vm.ctx.float_type()) {
@@ -108,10 +123,12 @@ impl PyFloat {
108123
}
109124
}
110125

126+
#[pymethod(name = "__abs__")]
111127
fn abs(&self, _vm: &VirtualMachine) -> f64 {
112128
self.value.abs()
113129
}
114130

131+
#[pymethod(name = "__add__")]
115132
fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
116133
let v1 = self.value;
117134
if objtype::isinstance(&other, &vm.ctx.float_type()) {
@@ -124,10 +141,17 @@ impl PyFloat {
124141
}
125142
}
126143

144+
#[pymethod(name = "__radd__")]
145+
fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
146+
self.add(other, vm)
147+
}
148+
149+
#[pymethod(name = "__bool__")]
127150
fn bool(&self, _vm: &VirtualMachine) -> bool {
128151
self.value != 0.0
129152
}
130153

154+
#[pymethod(name = "__divmod__")]
131155
fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
132156
if objtype::isinstance(&other, &vm.ctx.float_type())
133157
|| objtype::isinstance(&other, &vm.ctx.int_type())
@@ -140,14 +164,13 @@ impl PyFloat {
140164
}
141165
}
142166

167+
#[pymethod(name = "__floordiv__")]
143168
fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
144169
let v1 = self.value;
145170
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
146171
get_value(&other)
147172
} else if objtype::isinstance(&other, &vm.ctx.int_type) {
148-
objint::get_value(&other).to_f64().ok_or_else(|| {
149-
vm.new_overflow_error("int too large to convert to float".to_string())
150-
})?
173+
objint::get_float_value(&other, vm)?
151174
} else {
152175
return Ok(vm.ctx.not_implemented());
153176
};
@@ -163,12 +186,10 @@ impl PyFloat {
163186
let value = if objtype::isinstance(&arg, &vm.ctx.float_type()) {
164187
get_value(&arg)
165188
} else if objtype::isinstance(&arg, &vm.ctx.int_type()) {
166-
match objint::get_value(&arg).to_f64() {
167-
Some(f) => f,
168-
None => {
169-
return Err(
170-
vm.new_overflow_error("int too large to convert to float".to_string())
171-
);
189+
match objint::get_float_value(&arg, vm) {
190+
Ok(f) => f,
191+
Err(e) => {
192+
return Err(e);
172193
}
173194
}
174195
} else if objtype::isinstance(&arg, &vm.ctx.str_type()) {
@@ -199,29 +220,38 @@ impl PyFloat {
199220
PyFloat { value }.into_ref_with_type(vm, cls)
200221
}
201222

223+
#[pymethod(name = "__mod__")]
202224
fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
203225
let v1 = self.value;
204226
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
205227
get_value(&other)
206228
} else if objtype::isinstance(&other, &vm.ctx.int_type) {
207-
objint::get_value(&other).to_f64().ok_or_else(|| {
208-
vm.new_overflow_error("int too large to convert to float".to_string())
209-
})?
229+
objint::get_float_value(&other, vm)?
210230
} else {
211231
return Ok(vm.ctx.not_implemented());
212232
};
213233

214-
if v2 != 0.0 {
215-
Ok(vm.ctx.new_float(v1 % v2))
234+
mod_(v1, v2, vm)
235+
}
236+
237+
#[pymethod(name = "__rmod__")]
238+
fn rmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
239+
let v2 = self.value;
240+
let v1 = if objtype::isinstance(&other, &vm.ctx.int_type) {
241+
objint::get_float_value(&other, vm)?
216242
} else {
217-
Err(vm.new_zero_division_error("float mod by zero".to_string()))
218-
}
243+
return Ok(vm.ctx.not_implemented());
244+
};
245+
246+
mod_(v1, v2, vm)
219247
}
220248

249+
#[pymethod(name = "__neg__")]
221250
fn neg(&self, _vm: &VirtualMachine) -> f64 {
222251
-self.value
223252
}
224253

254+
#[pymethod(name = "__pow__")]
225255
fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
226256
let v1 = self.value;
227257
if objtype::isinstance(&other, &vm.ctx.float_type()) {
@@ -234,6 +264,7 @@ impl PyFloat {
234264
}
235265
}
236266

267+
#[pymethod(name = "__sub__")]
237268
fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
238269
let v1 = self.value;
239270
if objtype::isinstance(&other, &vm.ctx.float_type()) {
@@ -247,6 +278,7 @@ impl PyFloat {
247278
}
248279
}
249280

281+
#[pymethod(name = "__rsub__")]
250282
fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
251283
let v1 = self.value;
252284
if objtype::isinstance(&other, &vm.ctx.float_type()) {
@@ -260,18 +292,18 @@ impl PyFloat {
260292
}
261293
}
262294

295+
#[pymethod(name = "__repr__")]
263296
fn repr(&self, _vm: &VirtualMachine) -> String {
264297
self.value.to_string()
265298
}
266299

300+
#[pymethod(name = "__truediv__")]
267301
fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
268302
let v1 = self.value;
269303
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
270304
get_value(&other)
271305
} else if objtype::isinstance(&other, &vm.ctx.int_type) {
272-
objint::get_value(&other).to_f64().ok_or_else(|| {
273-
vm.new_overflow_error("int too large to convert to float".to_string())
274-
})?
306+
objint::get_float_value(&other, vm)?
275307
} else {
276308
return Ok(vm.ctx.not_implemented());
277309
};
@@ -283,14 +315,13 @@ impl PyFloat {
283315
}
284316
}
285317

318+
#[pymethod(name = "__rtruediv__")]
286319
fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
287320
let v1 = self.value;
288321
let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) {
289322
get_value(&other)
290323
} else if objtype::isinstance(&other, &vm.ctx.int_type) {
291-
objint::get_value(&other).to_f64().ok_or_else(|| {
292-
vm.new_overflow_error("int too large to convert to float".to_string())
293-
})?
324+
objint::get_float_value(&other, vm)?
294325
} else {
295326
return Ok(vm.ctx.not_implemented());
296327
};
@@ -302,6 +333,7 @@ impl PyFloat {
302333
}
303334
}
304335

336+
#[pymethod(name = "__mul__")]
305337
fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
306338
let v1 = self.value;
307339
if objtype::isinstance(&other, &vm.ctx.float_type) {
@@ -315,15 +347,38 @@ impl PyFloat {
315347
}
316348
}
317349

350+
#[pymethod(name = "__rmul__")]
351+
fn rmul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
352+
self.mul(other, vm)
353+
}
354+
355+
#[pymethod(name = "__trunc__")]
356+
fn trunc(&self, _vm: &VirtualMachine) -> BigInt {
357+
self.value.to_bigint().unwrap()
358+
}
359+
360+
#[pymethod(name = "__int__")]
361+
fn int(&self, vm: &VirtualMachine) -> BigInt {
362+
self.trunc(vm)
363+
}
364+
365+
#[pymethod(name = "__float__")]
366+
fn float(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyFloatRef {
367+
zelf
368+
}
369+
370+
#[pyproperty(name = "real")]
318371
fn real(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyFloatRef {
319372
zelf
320373
}
321374

375+
#[pymethod(name = "is_integer")]
322376
fn is_integer(&self, _vm: &VirtualMachine) -> bool {
323377
let v = self.value;
324378
(v - v.round()).abs() < std::f64::EPSILON
325379
}
326380

381+
#[pymethod(name = "as_integer_ratio")]
327382
fn as_integer_ratio(&self, vm: &VirtualMachine) -> PyResult {
328383
let value = self.value;
329384
if value.is_infinite() {
@@ -362,36 +417,10 @@ pub fn make_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<f64> {
362417

363418
#[rustfmt::skip] // to avoid line splitting
364419
pub fn init(context: &PyContext) {
365-
let float_type = &context.float_type;
366-
420+
PyFloat::extend_class(context, &context.float_type);
367421
let float_doc = "Convert a string or number to a floating point number, if possible.";
368-
369-
extend_class!(context, float_type, {
370-
"__eq__" => context.new_rustfunc(PyFloat::eq),
371-
"__lt__" => context.new_rustfunc(PyFloat::lt),
372-
"__le__" => context.new_rustfunc(PyFloat::le),
373-
"__gt__" => context.new_rustfunc(PyFloat::gt),
374-
"__ge__" => context.new_rustfunc(PyFloat::ge),
375-
"__abs__" => context.new_rustfunc(PyFloat::abs),
376-
"__add__" => context.new_rustfunc(PyFloat::add),
377-
"__radd__" => context.new_rustfunc(PyFloat::add),
378-
"__bool__" => context.new_rustfunc(PyFloat::bool),
379-
"__divmod__" => context.new_rustfunc(PyFloat::divmod),
380-
"__floordiv__" => context.new_rustfunc(PyFloat::floordiv),
422+
extend_class!(context, &context.float_type, {
381423
"__new__" => context.new_rustfunc(PyFloat::new_float),
382-
"__mod__" => context.new_rustfunc(PyFloat::mod_),
383-
"__neg__" => context.new_rustfunc(PyFloat::neg),
384-
"__pow__" => context.new_rustfunc(PyFloat::pow),
385-
"__sub__" => context.new_rustfunc(PyFloat::sub),
386-
"__rsub__" => context.new_rustfunc(PyFloat::rsub),
387-
"__repr__" => context.new_rustfunc(PyFloat::repr),
388424
"__doc__" => context.new_str(float_doc.to_string()),
389-
"__truediv__" => context.new_rustfunc(PyFloat::truediv),
390-
"__rtruediv__" => context.new_rustfunc(PyFloat::rtruediv),
391-
"__mul__" => context.new_rustfunc(PyFloat::mul),
392-
"__rmul__" => context.new_rustfunc(PyFloat::mul),
393-
"real" => context.new_property(PyFloat::real),
394-
"is_integer" => context.new_rustfunc(PyFloat::is_integer),
395-
"as_integer_ratio" => context.new_rustfunc(PyFloat::as_integer_ratio)
396425
});
397426
}

vm/src/obj/objint.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,12 @@ pub fn get_value(obj: &PyObjectRef) -> &BigInt {
535535
&obj.payload::<PyInt>().unwrap().value
536536
}
537537

538+
pub fn get_float_value(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<f64> {
539+
get_value(obj).to_f64().ok_or_else(|| {
540+
vm.new_overflow_error("OverflowError: int too large to convert to float".to_string())
541+
})
542+
}
543+
538544
#[inline]
539545
fn div_ints(vm: &VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult {
540546
if i2.is_zero() {

0 commit comments

Comments
 (0)