mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 11:14:38 +08:00
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.
This commit is contained in:
1 parent
3eda1cf3b4
commit
43d7c71a68
2 files changed
+32
-13
No files matched your search
Vendored
-2
@@ -1141,8 +1141,6 @@ class LongTest(unittest.TestCase):
|
||||
self.assertEqual((a ^ 63).bit_count(), 7)
|
||||
self.assertEqual(((a - 1) ^ 510).bit_count(), exp - 8)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_round(self):
|
||||
# check round-half-even algorithm. For round to nearest ten;
|
||||
# rounding map is invariant under adding multiples of 20
|
||||
|
||||
+32
-11
@@ -502,19 +502,40 @@ impl PyInt {
|
||||
#[pymethod(magic)]
|
||||
fn round(
|
||||
zelf: PyRef<Self>,
|
||||
precision: OptionalArg<PyObjectRef>,
|
||||
ndigits: OptionalArg<PyIntRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyRef<Self>> {
|
||||
match precision {
|
||||
OptionalArg::Missing => (),
|
||||
OptionalArg::Present(ref value) => {
|
||||
// Only accept int type ndigits
|
||||
let _ndigits = value.payload_if_subclass::<PyInt>(vm).ok_or_else(|| {
|
||||
vm.new_type_error(format!(
|
||||
"'{}' object cannot be interpreted as an integer",
|
||||
value.class().name()
|
||||
))
|
||||
})?;
|
||||
if let OptionalArg::Present(ndigits) = ndigits {
|
||||
let ndigits = ndigits.as_bigint();
|
||||
// round(12345, -2) == 12300
|
||||
// If precision >= 0, then any integer is already rounded correctly
|
||||
if let Some(ndigits) = ndigits.neg().to_u32() {
|
||||
if ndigits > 0 {
|
||||
// Work with positive integers and negate at the end if necessary
|
||||
let sign = if zelf.value.is_negative() {
|
||||
BigInt::from(-1)
|
||||
} else {
|
||||
BigInt::from(1)
|
||||
};
|
||||
let value = zelf.value.abs();
|
||||
|
||||
// Divide and multiply by the power of 10 to get the approximate answer
|
||||
let pow10 = BigInt::from(10).pow(ndigits);
|
||||
let quotient = &value / &pow10;
|
||||
let rounded = "ient * &pow10;
|
||||
|
||||
// Malachite division uses floor rounding, Python uses half-even
|
||||
let remainder = &value - &rounded;
|
||||
let halfpow10 = &pow10 / BigInt::from(2);
|
||||
let correction =
|
||||
if remainder > halfpow10 || (remainder == halfpow10 && quotient.is_odd()) {
|
||||
pow10
|
||||
} else {
|
||||
BigInt::from(0)
|
||||
};
|
||||
let rounded = (rounded + correction) * sign;
|
||||
return Ok(vm.ctx.new_int(rounded));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(zelf)
|
||||
|
||||
Reference in new issue
Block a user