Skip to content

Commit 5cc2eaf

Browse files
committed
math module
1 parent 966092b commit 5cc2eaf

File tree

20 files changed

+3376
-141
lines changed

20 files changed

+3376
-141
lines changed

.github/workflows/rust.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ jobs:
4343
- name: Run tests with FMA (macOS)
4444
if: matrix.os == 'macos-latest'
4545
run: cargo test --verbose --features mul_add && cargo test --release --verbose --features mul_add
46+
- name: Run tests with num-bigint
47+
run: cargo test --verbose --features num-bigint
48+
- name: Run tests with malachite-bigint
49+
run: cargo test --verbose --features malachite-bigint

Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
[package]
22
name = "pymath"
3-
version = "0.0.2"
3+
version = "0.0.3"
44
edition = "2024"
55
description = "A binary representation compatible Rust implementation of Python's math library."
66
license = "PSF-2.0"
77

88

99
[features]
10+
default = ["mul_add"]
11+
# complex = ["dep:num-complex"]
12+
num-bigint = ["_bigint", "dep:num-bigint"]
13+
malachite-bigint = ["_bigint", "dep:malachite-bigint"]
14+
_bigint = ["dep:num-traits", "dep:num-integer"] # Internal feature. User must use num-bigint or malachite-bigint instead.
15+
1016
# Do not enable this feature unless you really need it.
1117
# CPython didn't intend to use FMA for its math library.
1218
# This project uses this feature in CI to verify the code doesn't have additional bugs on aarch64-apple-darwin.
@@ -17,7 +23,12 @@ mul_add = []
1723

1824
[dependencies]
1925
libc = "0.2"
26+
num-complex = { version = "0.4", optional = true }
27+
num-bigint = { version = "0.4", optional = true }
28+
num-traits = { version = "0.2", optional = true }
29+
num-integer = { version = "0.1", optional = true }
30+
malachite-bigint = { version = "0.2", optional = true }
2031

2132
[dev-dependencies]
2233
proptest = "1.6.0"
23-
pyo3 = { version = "0.24", features = ["abi3"] }
34+
pyo3 = { version = "0.27", features = ["abi3", "auto-initialize"] }

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ A binary representation compatible Rust implementation of Python's math library.
1111

1212
Each function has been carefully translated from CPython's C implementation to Rust, preserving the same algorithms, constants, and corner case handling. The code maintains the same numerical properties, but in Rust!
1313

14+
## Module Structure
15+
16+
- `pymath::math` - Real number math functions (Python's `math` module)
17+
- `pymath::cmath` - Complex number functions (Python's `cmath` module, requires `complex` feature)
18+
- `pymath::m` - Direct libm bindings
19+
1420
## Usage
1521

1622
```rust
17-
use pymath::{gamma, lgamma};
23+
use pymath::math::{gamma, lgamma};
1824

1925
fn main() {
2026
// Get the same results as Python's math.gamma and math.lgamma

proptest-regressions/gamma.txt

Lines changed: 0 additions & 13 deletions
This file was deleted.

proptest-regressions/math.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Seeds for failure cases proptest has generated in the past. It is
2+
# automatically read and these particular cases re-run before any
3+
# novel cases are generated.
4+
#
5+
# It is recommended to check this file in to source control so that
6+
# everyone who runs the test benefits from these saved cases.
7+
cc 6f6becc96f663a83d20def559f516c7c5ce1a90b87c373d6c025dd3ab8f1fc39 # shrinks to x = 5.868849392888587e-309, y = 1.985586796867676e-308
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Seeds for failure cases proptest has generated in the past. It is
2+
# automatically read and these particular cases re-run before any
3+
# novel cases are generated.
4+
#
5+
# It is recommended to check this file in to source control so that
6+
# everyone who runs the test benefits from these saved cases.
7+
cc f58d401e380fa9d3a8dd5b137a668be6bd437776f47186a9479aee07c0aa28b8 # shrinks to p1 = 0.0, p2 = 0.0, q1 = -1.156587418587806e301, q2 = -1.315804087909368e-150
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Seeds for failure cases proptest has generated in the past. It is
2+
# automatically read and these particular cases re-run before any
3+
# novel cases are generated.
4+
#
5+
# It is recommended to check this file in to source control so that
6+
# everyone who runs the test benefits from these saved cases.
7+
cc c756e015f8e09d21d37e7f805e54f9270cac137e4570d10ecd5302d752e2559f # shrinks to x = -2.9225692145627912e-248, y = 1.565985096226121e-308
8+
cc 2f4b02dd2c2019dee35dcf980b7b4f077899289a7005bba21f849776768eda7a # shrinks to x = 6.095938323843682e143
9+
cc b52b190a89a473a7e5269f9a3efa83735318ae3bd52f3340c32bc4a40f4cda88 # shrinks to x = -0.0, y = -9.787367203123051e54
10+
cc a34a3387ec2547faa88352529f9730d47c6202f9b418a0b4474c9ebb850081e7 # shrinks to x = -1.3916042894622981e-207
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
#
55
# It is recommended to check this file in to source control so that
66
# everyone who runs the test benefits from these saved cases.
7-
cc 531a136f9fcde9d1da1ba5d173e62eee8ec8f7c877eb34abbc6d47611a641bc7 # shrinks to x = 0.0
7+
cc ac5c11a6ec8450aef2b03644e8b46a066f81c689b55f9657fcd0ae73d5829bc2 # shrinks to x = -1.8815128643365582

src/err.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,78 @@ impl TryFrom<c_int> for Error {
2020
}
2121
}
2222
}
23+
24+
/// Set errno to the given value.
25+
#[inline]
26+
pub(crate) fn set_errno(value: i32) {
27+
unsafe {
28+
#[cfg(target_os = "linux")]
29+
{
30+
*libc::__errno_location() = value;
31+
}
32+
#[cfg(target_os = "macos")]
33+
{
34+
*libc::__error() = value;
35+
}
36+
#[cfg(target_os = "windows")]
37+
{
38+
unsafe extern "C" {
39+
safe fn _errno() -> *mut i32;
40+
}
41+
*_errno() = value;
42+
}
43+
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
44+
{
45+
// FreeBSD, NetBSD, OpenBSD, etc. use __error()
46+
*libc::__error() = value;
47+
}
48+
}
49+
}
50+
51+
/// Get the current errno value.
52+
#[inline]
53+
pub(crate) fn get_errno() -> i32 {
54+
unsafe {
55+
#[cfg(target_os = "linux")]
56+
{
57+
*libc::__errno_location()
58+
}
59+
#[cfg(target_os = "macos")]
60+
{
61+
*libc::__error()
62+
}
63+
#[cfg(target_os = "windows")]
64+
{
65+
unsafe extern "C" {
66+
safe fn _errno() -> *mut i32;
67+
}
68+
*_errno()
69+
}
70+
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
71+
{
72+
// FreeBSD, NetBSD, OpenBSD, etc. use __error()
73+
*libc::__error()
74+
}
75+
}
76+
}
77+
78+
/// Check errno after libm call and convert to Result.
79+
#[inline]
80+
pub(crate) fn is_error(x: f64) -> Result<f64> {
81+
match get_errno() {
82+
0 => Ok(x),
83+
libc::EDOM => Err(Error::EDOM),
84+
libc::ERANGE => {
85+
// Underflow to zero is not an error.
86+
// Use 1.5 threshold to handle subnormal results that don't underflow to zero
87+
// (e.g., on Ubuntu/ia64) and to correctly detect underflows in expm1()
88+
// which may underflow toward -1.0 rather than 0.0. (bpo-46018)
89+
if x.abs() < 1.5 {
90+
Ok(x)
91+
} else {
92+
Err(Error::ERANGE)
93+
}
94+
}
95+
_ => Ok(x), // Unknown errno, just return the value
96+
}
97+
}

src/lib.rs

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1+
// Public modules
2+
pub mod math;
3+
4+
// Internal modules
15
mod err;
2-
mod gamma;
3-
mod m;
6+
pub(crate) mod m;
7+
mod m_sys;
48
#[cfg(test)]
59
mod test;
610

11+
// Re-export error types at root level
712
pub use err::{Error, Result};
8-
pub use gamma::{gamma, lgamma};
913

10-
macro_rules! libm {
11-
// Reset errno and handle errno when return type contains Result
12-
(fn $name:ident($arg:ident: $ty:ty) -> Result<$ret:ty>) => {
13-
#[inline(always)]
14-
pub fn $name($arg: $ty) -> Result<$ret> {
15-
errno::set_errno(errno::Errno(0));
16-
let r = unsafe { m::$name($arg) };
17-
crate::is_error(r)
18-
}
19-
};
20-
// Skip errno checking when return type is not Result
21-
(fn $name:ident($arg:ident: $ty:ty) -> $ret:ty) => {
22-
#[inline(always)]
23-
pub fn $name($arg: $ty) -> $ret {
24-
unsafe { m::$name($arg) }
25-
}
26-
};
14+
/// Fused multiply-add operation.
15+
/// When `mul_add` feature is enabled, uses hardware FMA instruction.
16+
/// Otherwise, uses separate multiply and add operations.
17+
#[inline(always)]
18+
pub(crate) fn mul_add(a: f64, b: f64, c: f64) -> f64 {
19+
if cfg!(feature = "mul_add") {
20+
a.mul_add(b, c)
21+
} else {
22+
a * b + c
23+
}
2724
}
2825

2926
macro_rules! pyo3_proptest {
@@ -34,8 +31,7 @@ macro_rules! pyo3_proptest {
3431

3532
let rs_result = $fn_name(x);
3633

37-
pyo3::prepare_freethreaded_python();
38-
Python::with_gil(|py| {
34+
pyo3::Python::attach(|py| {
3935
let math = PyModule::import(py, "math").unwrap();
4036
let py_func = math
4137
.getattr(stringify!($fn_name))
@@ -60,8 +56,7 @@ macro_rules! pyo3_proptest {
6056

6157
let rs_result = Ok($fn_name(x));
6258

63-
pyo3::prepare_freethreaded_python();
64-
Python::with_gil(|py| {
59+
pyo3::Python::attach(|py| {
6560
let math = PyModule::import(py, "math").unwrap();
6661
let py_func = math
6762
.getattr(stringify!($fn_name))
@@ -101,20 +96,4 @@ macro_rules! pyo3_proptest {
10196
};
10297
}
10398

104-
libm!(fn erf(n: f64) -> f64);
105-
pyo3_proptest!(erf(_), test_erf, proptest_erf, edgetest_erf);
106-
107-
libm!(fn erfc(n: f64) -> f64);
108-
pyo3_proptest!(erfc(_), test_erfc, proptest_erfc, edgetest_erfc);
109-
110-
/// Call is_error when errno != 0, and where x is the result libm
111-
/// returned. is_error will usually set up an exception and return
112-
/// true (1), but may return false (0) without setting up an exception.
113-
// fn is_error(x: f64) -> crate::Result<f64> {
114-
// match errno::errno() {
115-
// errno::Errno(0) => Ok(x),
116-
// errno::Errno(libc::ERANGE) if x.abs() < 1.5 => Ok(0f64),
117-
// errno::Errno(errno) => Err(errno.try_into().unwrap()),
118-
// }
119-
// }
12099
use pyo3_proptest;

0 commit comments

Comments
 (0)