mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Handle math.log bases without premature float conversion (#9019)
Use the natural logarithm path for both operands, evaluating x first. Preserve large integer bases and raise ZeroDivisionError for base one while retaining noninteger conversion errors. Split the Python 3.14-compatible behavior from #8954 without its additional __index__ fallback or changes to log2 and log10. Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
1 parent
48419bd114
commit
bd8fdb7e5c
2 files changed
+84
-26
No files matched your search
+27
-26
@@ -94,35 +94,36 @@ mod math {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn log(x: PyObjectRef, base: OptionalArg<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
let base = base.into_option().map(|v| v.into_float());
|
||||
// Check base first for proper error messages
|
||||
if let Some(b) = base {
|
||||
if b <= 0.0 {
|
||||
return Err(vm.new_value_error(format!(
|
||||
fn log(x: PyObjectRef, base: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
let natural_log = |x: &PyObject| {
|
||||
// Handle BigInt specially for large values (only for actual int type, not float)
|
||||
if let Some(i) = x.downcast_ref::<PyInt>() {
|
||||
return pymath::math::log_bigint(i.as_bigint(), None).map_err(|err| match err {
|
||||
pymath::Error::EDOM => vm.new_value_error("expected a positive input"),
|
||||
_ => pymath_exception(err, vm),
|
||||
});
|
||||
}
|
||||
let val = x.try_float(vm)?.to_f64();
|
||||
pymath::math::log(val, None).map_err(|err| match err {
|
||||
pymath::Error::EDOM => vm.new_value_error(format!(
|
||||
"expected a positive input, got {}",
|
||||
super::float_repr(b)
|
||||
)));
|
||||
}
|
||||
if b == 1.0 {
|
||||
return Err(vm.new_value_error("math domain error"));
|
||||
}
|
||||
}
|
||||
// Handle BigInt specially for large values (only for actual int type, not float)
|
||||
if let Some(i) = x.downcast_ref::<PyInt>() {
|
||||
return pymath::math::log_bigint(i.as_bigint(), base).map_err(|err| match err {
|
||||
pymath::Error::EDOM => vm.new_value_error("expected a positive input"),
|
||||
super::float_repr(val)
|
||||
)),
|
||||
_ => pymath_exception(err, vm),
|
||||
});
|
||||
})
|
||||
};
|
||||
let numerator = natural_log(&x)?;
|
||||
match base {
|
||||
OptionalArg::Missing => Ok(numerator),
|
||||
OptionalArg::Present(base) => {
|
||||
let denominator = natural_log(&base)?;
|
||||
if denominator == 0.0 {
|
||||
Err(vm.new_zero_division_error("division by zero"))
|
||||
} else {
|
||||
Ok(numerator / denominator)
|
||||
}
|
||||
}
|
||||
}
|
||||
let val = x.try_float(vm)?.to_f64();
|
||||
pymath::math::log(val, base).map_err(|err| match err {
|
||||
pymath::Error::EDOM => vm.new_value_error(format!(
|
||||
"expected a positive input, got {}",
|
||||
super::float_repr(val)
|
||||
)),
|
||||
_ => pymath_exception(err, vm),
|
||||
})
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
|
||||
@@ -17,6 +17,63 @@ NINF = float("-inf")
|
||||
#
|
||||
# assert math.log(1.1111) == math.log(Conversible())
|
||||
|
||||
# A logarithm can fit in a float even when its integer base cannot.
|
||||
assert math.isclose(math.log(2**4000, 2**2000), 2.0)
|
||||
assert math.isclose(math.log(2.0, 2**2000), 1.0 / 2000)
|
||||
|
||||
for value in (1, 8.0, INF, NAN):
|
||||
assert_raises(ZeroDivisionError, math.log, value, 1)
|
||||
assert_raises(TypeError, math.log, object(), 1)
|
||||
assert_raises(ValueError, math.log, 8, -(2**2000))
|
||||
|
||||
log_conversions = []
|
||||
|
||||
|
||||
class LogFloat:
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def __float__(self):
|
||||
log_conversions.append(self.name)
|
||||
return self.value
|
||||
|
||||
|
||||
assert math.isclose(math.log(LogFloat("x", 8.0), LogFloat("base", 2.0)), 3.0)
|
||||
assert log_conversions == ["x", "base"]
|
||||
log_conversions.clear()
|
||||
assert_raises(ValueError, math.log, -1, LogFloat("base", 2.0))
|
||||
assert log_conversions == []
|
||||
|
||||
|
||||
class LogFloatOverflow:
|
||||
def __float__(self):
|
||||
raise OverflowError("float conversion failed")
|
||||
|
||||
def __index__(self):
|
||||
raise AssertionError("must not retry __index__ after __float__ fails")
|
||||
|
||||
|
||||
assert_raises(OverflowError, math.log, LogFloatOverflow())
|
||||
assert_raises(OverflowError, math.log, 8, LogFloatOverflow())
|
||||
|
||||
|
||||
class LogIndexOnly:
|
||||
def __index__(self):
|
||||
return 2**2000
|
||||
|
||||
|
||||
assert_raises(OverflowError, math.log, LogIndexOnly())
|
||||
assert_raises(OverflowError, math.log, 8, LogIndexOnly())
|
||||
|
||||
|
||||
class LogInt(int):
|
||||
def __float__(self):
|
||||
raise AssertionError("integer logarithms use the native integer value")
|
||||
|
||||
|
||||
assert math.isclose(math.log(LogInt(2**4000), LogInt(2**2000)), 2.0)
|
||||
|
||||
# roundings
|
||||
assert int.__trunc__
|
||||
assert int.__floor__
|
||||
|
||||
Reference in new issue
Block a user