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
15 changes: 5 additions & 10 deletions crates/stdlib/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ use crate::vm::{VirtualMachine, builtins::PyBaseExceptionRef};
#[pymodule]
mod math {
use crate::vm::{
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine,
AsObject, Py, PyObject, PyObjectRef, PyResult, VirtualMachine,
builtins::{
PyFloat, PyInt, PyIntRef, PyModule, PyStrInterned, try_bigint_to_f64, try_f64_to_bigint,
},
function::{
ArgIndex, ArgIntoFloat, ArgIterable, Either, NameCoordinates, OptionalArg, PosArgs,
},
function::{ArgIndex, ArgIntoFloat, ArgIterable, NameCoordinates, OptionalArg, PosArgs},
identifier,
};
use malachite_bigint::BigInt;
Expand Down Expand Up @@ -457,7 +455,7 @@ mod math {
#[derive(FromArgs)]
struct LdexpArgs {
#[pyarg(positional)]
x: Either<PyRef<PyFloat>, PyIntRef>,
x: ArgIntoFloat,
// Refuses anything that is not an `int`, including objects with `__index__`.
#[pyarg(positional, error_msg = "Expected an int as second argument to ldexp.")]
i: PyIntRef,
Expand All @@ -466,11 +464,8 @@ mod math {
#[pyfunction]
fn ldexp(args: LdexpArgs, vm: &VirtualMachine) -> PyResult<f64> {
let LdexpArgs { x, i } = args;
let value = match x {
Either::A(f) => f.to_f64(),
Either::B(z) => try_bigint_to_f64(z.as_bigint(), vm)?,
};
pymath::math::ldexp_bigint(value, i.as_bigint()).map_err(|err| pymath_exception(err, vm))
pymath::math::ldexp_bigint(x.into_float(), i.as_bigint())
.map_err(|err| pymath_exception(err, vm))
}

#[pyfunction]
Expand Down
8 changes: 7 additions & 1 deletion crates/vm/src/function/number.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::argument::OptionalArg;
use crate::{
AsObject, Py, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyInt, PyIntRef},
builtins::{PyFloat, PyInt, PyIntRef},
};
use core::ops::Deref;
use malachite_bigint::BigInt;
Expand Down Expand Up @@ -88,6 +88,12 @@ impl From<ArgIntoFloat> for f64 {
impl TryFromObject for ArgIntoFloat {
// Equivalent to PyFloat_AsDouble.
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
// A float subclass is read directly, without calling its __float__.
if let Some(float) = obj.downcast_ref::<PyFloat>() {
return Ok(Self {
value: float.to_f64(),
});
}
let value = obj.try_float(vm)?.to_f64();
Ok(Self { value })
}
Expand Down
30 changes: 30 additions & 0 deletions extra_tests/snippets/stdlib_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,33 @@ def assertAllNotClose(examples, *args, **kwargs):
assert math.perm(5) == math.perm(5, None) == 120
assert math.perm(2**64, 0) == 1
assert math.comb(2**64, 2**64 - 1) == 2**64


class LdexpFloat:
def __float__(self):
return 1.5


class LdexpIndex:
def __index__(self):
return 3


class LdexpRaisingFloat:
def __float__(self):
raise RuntimeError


assert math.ldexp(LdexpFloat(), 1) == 3.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 Honor the restriction on modifying existing test files

This regression block adds new test logic and assertions to an existing test file, whereas the root AGENTS.md explicitly limits test-file modifications to adding/removing @unittest.expectedFailure decorators and removing associated TODO comments. Revert these additions or obtain an explicit exception before including them, since they fall outside the permitted test-change scope.

AGENTS.md reference: AGENTS.md:L280-L282

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That section covers the CPython tests in Lib/test. For extra_tests/snippets, AGENTS.md asks for new tests in the most closely related existing file (L119-L120), which is what this does. None of these cases is in Lib/test/test_math.py: testLdexp only passes int and float mantissas.

assert math.ldexp(LdexpIndex(), 1) == 6.0
assert_raises(RuntimeError, math.ldexp, LdexpRaisingFloat(), 1)
assert_raises(OverflowError, math.ldexp, 10**400, 1)


class FloatSubclass(float):
def __float__(self):
return 9.0


assert math.sqrt(FloatSubclass(4.0)) == 2.0
assert math.ldexp(FloatSubclass(1.0), 1) == 2.0
Loading