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:
youknowdot authored and GitHub committed 2026-10-09 20:56:40 +09:00
1 parent 48419bd114
commit bd8fdb7e5c
2 files changed
+84 -26

No files matched your search

+27 -26
View File
@@ -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]
+57
View File
@@ -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__