Skip to content

Commit 43d7c71

Browse files
committed
Fix integer rounding
The [docs](https://docs.python.org/3/library/functions.html#round) specify that calling `round` with a negative precision removes significant digits, so that `round(12345, -2) == 12300`. The implementation was simply returning the original integer. Additionally, `round(a, b)` is implemented as `(a / 10^b) * 10^b`, using half-even rounding during the division.
1 parent 3eda1cf commit 43d7c71

File tree

2 files changed

+32
-13
lines changed

2 files changed

+32
-13
lines changed

Lib/test/test_long.py

-2
Original file line numberDiff line numberDiff line change
@@ -1141,8 +1141,6 @@ def test_bit_count(self):
11411141
self.assertEqual((a ^ 63).bit_count(), 7)
11421142
self.assertEqual(((a - 1) ^ 510).bit_count(), exp - 8)
11431143

1144-
# TODO: RUSTPYTHON
1145-
@unittest.expectedFailure
11461144
def test_round(self):
11471145
# check round-half-even algorithm. For round to nearest ten;
11481146
# rounding map is invariant under adding multiples of 20

vm/src/builtins/int.rs

+32-11
Original file line numberDiff line numberDiff line change
@@ -502,19 +502,40 @@ impl PyInt {
502502
#[pymethod(magic)]
503503
fn round(
504504
zelf: PyRef<Self>,
505-
precision: OptionalArg<PyObjectRef>,
505+
ndigits: OptionalArg<PyIntRef>,
506506
vm: &VirtualMachine,
507507
) -> PyResult<PyRef<Self>> {
508-
match precision {
509-
OptionalArg::Missing => (),
510-
OptionalArg::Present(ref value) => {
511-
// Only accept int type ndigits
512-
let _ndigits = value.payload_if_subclass::<PyInt>(vm).ok_or_else(|| {
513-
vm.new_type_error(format!(
514-
"'{}' object cannot be interpreted as an integer",
515-
value.class().name()
516-
))
517-
})?;
508+
if let OptionalArg::Present(ndigits) = ndigits {
509+
let ndigits = ndigits.as_bigint();
510+
// round(12345, -2) == 12300
511+
// If precision >= 0, then any integer is already rounded correctly
512+
if let Some(ndigits) = ndigits.neg().to_u32() {
513+
if ndigits > 0 {
514+
// Work with positive integers and negate at the end if necessary
515+
let sign = if zelf.value.is_negative() {
516+
BigInt::from(-1)
517+
} else {
518+
BigInt::from(1)
519+
};
520+
let value = zelf.value.abs();
521+
522+
// Divide and multiply by the power of 10 to get the approximate answer
523+
let pow10 = BigInt::from(10).pow(ndigits);
524+
let quotient = &value / &pow10;
525+
let rounded = &quotient * &pow10;
526+
527+
// Malachite division uses floor rounding, Python uses half-even
528+
let remainder = &value - &rounded;
529+
let halfpow10 = &pow10 / BigInt::from(2);
530+
let correction =
531+
if remainder > halfpow10 || (remainder == halfpow10 && quotient.is_odd()) {
532+
pow10
533+
} else {
534+
BigInt::from(0)
535+
};
536+
let rounded = (rounded + correction) * sign;
537+
return Ok(vm.ctx.new_int(rounded));
538+
}
518539
}
519540
}
520541
Ok(zelf)

0 commit comments

Comments
 (0)