Skip to content

Commit 825cf5b

Browse files
committed
math module
1 parent 966092b commit 825cf5b

File tree

17 files changed

+3080
-99
lines changed

17 files changed

+3080
-99
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/lib.txt

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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

src/err.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,65 @@ 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+
}
44+
}
45+
46+
/// Get the current errno value.
47+
#[inline]
48+
pub(crate) fn get_errno() -> i32 {
49+
unsafe {
50+
#[cfg(target_os = "linux")]
51+
{
52+
*libc::__errno_location()
53+
}
54+
#[cfg(target_os = "macos")]
55+
{
56+
*libc::__error()
57+
}
58+
#[cfg(target_os = "windows")]
59+
{
60+
unsafe extern "C" {
61+
safe fn _errno() -> *mut i32;
62+
}
63+
*_errno()
64+
}
65+
}
66+
}
67+
68+
/// Check errno after libm call and convert to Result.
69+
#[inline]
70+
pub(crate) fn is_error(x: f64) -> Result<f64> {
71+
match get_errno() {
72+
0 => Ok(x),
73+
libc::EDOM => Err(Error::EDOM),
74+
libc::ERANGE => {
75+
// Underflow to zero is not an error
76+
if x.abs() < 1.5 {
77+
Ok(x)
78+
} else {
79+
Err(Error::ERANGE)
80+
}
81+
}
82+
_ => Ok(x), // Unknown errno, just return the value
83+
}
84+
}

src/lib.rs

Lines changed: 17 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
1+
// Public modules
2+
pub mod math;
3+
4+
// Internal modules
15
mod err;
2-
mod gamma;
36
mod m;
47
#[cfg(test)]
58
mod test;
69

10+
// Re-export error types at root level
711
pub use err::{Error, Result};
8-
pub use gamma::{gamma, lgamma};
912

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-
};
13+
/// Fused multiply-add operation.
14+
/// When `mul_add` feature is enabled, uses hardware FMA instruction.
15+
/// Otherwise, uses separate multiply and add operations.
16+
#[inline(always)]
17+
pub(crate) fn mul_add(a: f64, b: f64, c: f64) -> f64 {
18+
if cfg!(feature = "mul_add") {
19+
a.mul_add(b, c)
20+
} else {
21+
a * b + c
22+
}
2723
}
2824

2925
macro_rules! pyo3_proptest {
@@ -34,8 +30,7 @@ macro_rules! pyo3_proptest {
3430

3531
let rs_result = $fn_name(x);
3632

37-
pyo3::prepare_freethreaded_python();
38-
Python::with_gil(|py| {
33+
pyo3::Python::attach(|py| {
3934
let math = PyModule::import(py, "math").unwrap();
4035
let py_func = math
4136
.getattr(stringify!($fn_name))
@@ -60,8 +55,7 @@ macro_rules! pyo3_proptest {
6055

6156
let rs_result = Ok($fn_name(x));
6257

63-
pyo3::prepare_freethreaded_python();
64-
Python::with_gil(|py| {
58+
pyo3::Python::attach(|py| {
6559
let math = PyModule::import(py, "math").unwrap();
6660
let py_func = math
6761
.getattr(stringify!($fn_name))
@@ -101,20 +95,4 @@ macro_rules! pyo3_proptest {
10195
};
10296
}
10397

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-
// }
12098
use pyo3_proptest;

src/m.rs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,69 @@
44
// or by `compiler-builtins` on unsupported platforms.
55
#[allow(dead_code)]
66
unsafe extern "C" {
7+
// Trigonometric functions
78
pub fn acos(n: f64) -> f64;
89
pub fn asin(n: f64) -> f64;
910
pub fn atan(n: f64) -> f64;
1011
pub fn atan2(a: f64, b: f64) -> f64;
11-
pub fn cbrt(n: f64) -> f64;
12-
pub fn cbrtf(n: f32) -> f32;
12+
pub fn cos(n: f64) -> f64;
13+
pub fn sin(n: f64) -> f64;
14+
pub fn tan(n: f64) -> f64;
15+
16+
// Hyperbolic functions
17+
pub fn acosh(n: f64) -> f64;
18+
pub fn asinh(n: f64) -> f64;
19+
pub fn atanh(n: f64) -> f64;
1320
pub fn cosh(n: f64) -> f64;
21+
pub fn sinh(n: f64) -> f64;
22+
pub fn tanh(n: f64) -> f64;
23+
24+
// Exponential and logarithmic functions
25+
pub fn exp(n: f64) -> f64;
26+
pub fn exp2(n: f64) -> f64;
1427
pub fn expm1(n: f64) -> f64;
1528
pub fn expm1f(n: f32) -> f32;
16-
pub fn fdim(a: f64, b: f64) -> f64;
17-
pub fn fdimf(a: f32, b: f32) -> f32;
29+
pub fn log(n: f64) -> f64;
30+
pub fn log10(n: f64) -> f64;
31+
pub fn log1p(n: f64) -> f64;
32+
pub fn log1pf(n: f32) -> f32;
33+
pub fn log2(n: f64) -> f64;
34+
35+
// Power functions
36+
pub fn cbrt(n: f64) -> f64;
37+
pub fn cbrtf(n: f32) -> f32;
1838
#[cfg_attr(target_env = "msvc", link_name = "_hypot")]
1939
pub fn hypot(x: f64, y: f64) -> f64;
2040
#[cfg_attr(target_env = "msvc", link_name = "_hypotf")]
2141
pub fn hypotf(x: f32, y: f32) -> f32;
22-
pub fn log1p(n: f64) -> f64;
23-
pub fn log1pf(n: f32) -> f32;
24-
pub fn sinh(n: f64) -> f64;
25-
pub fn tan(n: f64) -> f64;
26-
pub fn tanh(n: f64) -> f64;
27-
pub fn tgamma(n: f64) -> f64;
28-
pub fn tgammaf(n: f32) -> f32;
29-
pub fn lgamma_r(n: f64, s: &mut i32) -> f64;
30-
#[cfg(not(target_os = "aix"))]
31-
pub fn lgammaf_r(n: f32, s: &mut i32) -> f32;
42+
pub fn pow(x: f64, y: f64) -> f64;
43+
pub fn sqrt(n: f64) -> f64;
44+
45+
// Floating-point manipulation functions
46+
pub fn ceil(n: f64) -> f64;
47+
pub fn copysign(x: f64, y: f64) -> f64;
48+
pub fn fabs(n: f64) -> f64;
49+
pub fn fdim(a: f64, b: f64) -> f64;
50+
pub fn fdimf(a: f32, b: f32) -> f32;
51+
pub fn floor(n: f64) -> f64;
52+
pub fn fmod(x: f64, y: f64) -> f64;
53+
pub fn frexp(n: f64, exp: *mut i32) -> f64;
54+
pub fn ldexp(x: f64, n: i32) -> f64;
55+
pub fn modf(n: f64, iptr: *mut f64) -> f64;
56+
pub fn nextafter(x: f64, y: f64) -> f64;
57+
pub fn remainder(x: f64, y: f64) -> f64;
58+
pub fn trunc(n: f64) -> f64;
59+
60+
// Special functions
3261
pub fn erf(n: f64) -> f64;
33-
pub fn erff(n: f32) -> f32;
3462
pub fn erfc(n: f64) -> f64;
63+
pub fn erff(n: f32) -> f32;
3564
pub fn erfcf(n: f32) -> f32;
65+
pub fn lgamma_r(n: f64, s: &mut i32) -> f64;
66+
#[cfg(not(target_os = "aix"))]
67+
pub fn lgammaf_r(n: f32, s: &mut i32) -> f32;
68+
pub fn tgamma(n: f64) -> f64;
69+
pub fn tgammaf(n: f32) -> f32;
3670

3771
// pub fn acosf128(n: f128) -> f128;
3872
// pub fn asinf128(n: f128) -> f128;

0 commit comments

Comments
 (0)