Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 27 additions & 26 deletions crates/stdlib/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
"expected a positive input, got {}",
super::float_repr(b)
)));
}
if b == 1.0 {
return Err(vm.new_value_error("math domain error"));
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),
});
}
}
// 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"),
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(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]
Expand Down
57 changes: 57 additions & 0 deletions extra_tests/snippets/stdlib_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,63 @@
#
# 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the forbidden test additions

This block adds new assertions, helper classes, and test data to extra_tests/snippets/stdlib_math.py, but the repository's critical test-code rules forbid modifying test assertions, logic, or data and state that the only acceptable test-file changes are adding or removing expected-failure decorators. Remove this new test logic from the patch (or obtain a documented policy exception) before merging.

AGENTS.md reference: AGENTS.md:L273-L279

Useful? React with 👍 / 👎.

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__
Expand Down
Loading