Skip to content

Commit 77ee119

Browse files
committed
fix a lot
1 parent 243afd0 commit 77ee119

File tree

11 files changed

+562
-233
lines changed

11 files changed

+562
-233
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "pymath"
33
author = "Jeong, YunWon <[email protected]>"
44
repository = "https://github.com/RustPython/pymath"
55
description = "A binary representation compatible Rust implementation of Python's math library."
6-
version = "0.1.1"
6+
version = "0.1.2"
77
edition = "2024"
88
license = "PSF-2.0"
99

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ Every function produces identical results to Python at the binary representation
5757
- [x] `nextafter`
5858
- [x] `pi`
5959
- [x] `pow`
60-
- [x] `prod`
60+
- [x] `prod` (`prod`, `prod_int`)
6161
- [x] `radians`
6262
- [x] `remainder`
6363
- [x] `sin`
6464
- [x] `sinh`
6565
- [x] `sqrt`
66-
- [x] `sumprod` (`sumprod`, `sumprod_int`, `sumprod_generic`)
66+
- [x] `sumprod` (`sumprod`, `sumprod_int`)
6767
- [x] `tan`
6868
- [x] `tanh`
6969
- [x] `tau`

proptest-regressions/cmath/trigonometric.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ cc 409f359905a7e77eacdbd4643c3a1483cc128e46a1c06795c246f3d57a2e61b9 # shrinks to
99
cc 5acbcf76c3bcbaf0b2b89a987e38c75265434f96fdc11598830e2221df72fc53 # shrinks to re = 0.00010260965539526095, im = 4.984721877290597e-19
1010
cc ac04191f916633de0408e071f5f6ada8f7fe7a1be02caca7a32f37ecb148a5ac # shrinks to re = 3.049837651806167e74, im = -2.222842222335753e-166
1111
cc 77ec3c08d2e057fb9467eb13c3b953e4e30f2800259d3653f7bcdfcbaf53614f # shrinks to re = 7.812038268590211e52, im = -2.623972069152808e-109
12+
cc c8fa2873ca664202a38d40ef3766b0f9f07dc82c1d5cd08dcd6d180564a013a1 # shrinks to re = -0.0016106222874309162, im = 0.10954881424244108

proptest-regressions/math/misc.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 46166a18f59569bb11107fd280e53c8f37902760e15afbe67d6951dcd3032c72 # shrinks to x = 0.0, y = 0.0, z = 0.0

src/cmath/exponential.rs

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,10 @@ pub fn exp(z: Complex64) -> Result<Complex64> {
131131
Ok(Complex64::new(r_re, r_im))
132132
}
133133

134-
/// Complex natural logarithm.
134+
/// Complex natural logarithm (ln, base e).
135+
/// TODO: consider to expose API
135136
#[inline]
136-
pub fn log(z: Complex64) -> Result<Complex64> {
137+
pub(crate) fn ln(z: Complex64) -> Result<Complex64> {
137138
special_value!(z, LOG_SPECIAL_VALUES);
138139

139140
let ax = m::fabs(z.re);
@@ -167,10 +168,48 @@ pub fn log(z: Complex64) -> Result<Complex64> {
167168
Ok(Complex64::new(r_re, r_im))
168169
}
169170

171+
/// Complex logarithm with optional base.
172+
///
173+
/// If base is None, returns the natural logarithm.
174+
/// If base is Some(b), returns log(z) / log(b).
175+
#[inline]
176+
pub fn log(z: Complex64, base: Option<Complex64>) -> Result<Complex64> {
177+
let log_z = ln(z)?;
178+
match base {
179+
None => Ok(log_z),
180+
Some(b) => {
181+
let log_b = ln(b)?;
182+
// Use _Py_c_quot-style division to preserve sign of zero
183+
Ok(c_quot(log_z, log_b))
184+
}
185+
}
186+
}
187+
188+
/// Complex division following _Py_c_quot algorithm.
189+
/// This preserves the sign of zero correctly.
190+
#[inline]
191+
fn c_quot(a: Complex64, b: Complex64) -> Complex64 {
192+
let abs_breal = m::fabs(b.re);
193+
let abs_bimag = m::fabs(b.im);
194+
195+
if abs_breal >= abs_bimag {
196+
if abs_breal == 0.0 {
197+
return Complex64::new(f64::NAN, f64::NAN);
198+
}
199+
let ratio = b.im / b.re;
200+
let denom = b.re + b.im * ratio;
201+
Complex64::new((a.re + a.im * ratio) / denom, (a.im - a.re * ratio) / denom)
202+
} else {
203+
let ratio = b.re / b.im;
204+
let denom = b.re * ratio + b.im;
205+
Complex64::new((a.re * ratio + a.im) / denom, (a.im * ratio - a.re) / denom)
206+
}
207+
}
208+
170209
/// Complex base-10 logarithm.
171210
#[inline]
172211
pub fn log10(z: Complex64) -> Result<Complex64> {
173-
let r = log(z)?;
212+
let r = ln(z)?;
174213
Ok(Complex64::new(r.re / M_LN10, r.im / M_LN10))
175214
}
176215

@@ -191,13 +230,61 @@ mod tests {
191230
fn test_exp(re: f64, im: f64) {
192231
test_cmath_func("exp", exp, re, im);
193232
}
194-
fn test_log(re: f64, im: f64) {
195-
test_cmath_func("log", log, re, im);
233+
fn test_log_without_base(re: f64, im: f64) {
234+
test_cmath_func("log", |z| log(z, None), re, im);
196235
}
197236
fn test_log10(re: f64, im: f64) {
198237
test_cmath_func("log10", log10, re, im);
199238
}
200239

240+
/// Test log with base - compares with Python's cmath.log(z, base)
241+
fn test_log(z_re: f64, z_im: f64, base_re: f64, base_im: f64) {
242+
use pyo3::prelude::*;
243+
244+
let z = Complex64::new(z_re, z_im);
245+
let base = Complex64::new(base_re, base_im);
246+
let rs_result = log(z, Some(base));
247+
248+
pyo3::Python::attach(|py| {
249+
let cmath = pyo3::types::PyModule::import(py, "cmath").unwrap();
250+
let py_z = pyo3::types::PyComplex::from_doubles(py, z_re, z_im);
251+
let py_base = pyo3::types::PyComplex::from_doubles(py, base_re, base_im);
252+
let py_result = cmath.getattr("log").unwrap().call1((py_z, py_base));
253+
254+
match py_result {
255+
Ok(result) => {
256+
use pyo3::types::PyComplexMethods;
257+
let c = result.cast::<pyo3::types::PyComplex>().unwrap();
258+
let py_re = c.real();
259+
let py_im = c.imag();
260+
match rs_result {
261+
Ok(rs) => {
262+
crate::cmath::tests::assert_complex_eq(
263+
py_re,
264+
py_im,
265+
rs,
266+
"log_with_base",
267+
z_re,
268+
z_im,
269+
);
270+
}
271+
Err(e) => {
272+
panic!(
273+
"log({z_re}+{z_im}j, {base_re}+{base_im}j): py=({py_re}, {py_im}) but rs returned error {e:?}"
274+
);
275+
}
276+
}
277+
}
278+
Err(_) => {
279+
assert!(
280+
rs_result.is_err(),
281+
"log({z_re}+{z_im}j, {base_re}+{base_im}j): py raised error but rs={rs_result:?}"
282+
);
283+
}
284+
}
285+
});
286+
}
287+
201288
use crate::test::EDGE_VALUES;
202289

203290
#[test]
@@ -222,7 +309,7 @@ mod tests {
222309
fn edgetest_log() {
223310
for &re in &EDGE_VALUES {
224311
for &im in &EDGE_VALUES {
225-
test_log(re, im);
312+
test_log_without_base(re, im);
226313
}
227314
}
228315
}
@@ -236,6 +323,25 @@ mod tests {
236323
}
237324
}
238325

326+
#[test]
327+
fn edgetest_log_with_base() {
328+
// Test log with various bases - sign preservation edge cases
329+
let bases = [0.5, 2.0, 10.0];
330+
let values = [0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0];
331+
for &base in &bases {
332+
for &v in &values {
333+
test_log(v, 0.0, base, 0.0);
334+
}
335+
}
336+
// Additional edge cases with imaginary parts
337+
for &z_re in &EDGE_VALUES {
338+
for &z_im in &EDGE_VALUES {
339+
test_log(z_re, z_im, 2.0, 0.0);
340+
test_log(z_re, z_im, 0.5, 0.0);
341+
}
342+
}
343+
}
344+
239345
proptest::proptest! {
240346
#[test]
241347
fn proptest_sqrt(re: f64, im: f64) {
@@ -249,7 +355,7 @@ mod tests {
249355

250356
#[test]
251357
fn proptest_log(re: f64, im: f64) {
252-
test_log(re, im);
358+
test_log_without_base(re, im);
253359
}
254360

255361
#[test]

src/cmath/trigonometric.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ static SINH_SPECIAL_VALUES: [[Complex64; 7]; 7] = [
8383
static TANH_SPECIAL_VALUES: [[Complex64; 7]; 7] = [
8484
[c(-1.0, 0.0), c(U, U), c(-1.0, -0.0), c(-1.0, 0.0), c(U, U), c(-1.0, 0.0), c(-1.0, 0.0)],
8585
[c(N, N), c(U, U), c(U, U), c(U, U), c(U, U), c(N, N), c(N, N)],
86-
[c(-0.0, N), c(U, U), c(-0.0, -0.0), c(-0.0, 0.0), c(U, U), c(-0.0, N), c(-0.0, N)],
87-
[c(0.0, N), c(U, U), c(0.0, -0.0), c(0.0, 0.0), c(U, U), c(0.0, N), c(0.0, N)],
86+
[c(N, N), c(U, U), c(-0.0, -0.0), c(-0.0, 0.0), c(U, U), c(N, N), c(N, N)],
87+
[c(N, N), c(U, U), c(0.0, -0.0), c(0.0, 0.0), c(U, U), c(N, N), c(N, N)],
8888
[c(N, N), c(U, U), c(U, U), c(U, U), c(U, U), c(N, N), c(N, N)],
8989
[c(1.0, 0.0), c(U, U), c(1.0, -0.0), c(1.0, 0.0), c(U, U), c(1.0, 0.0), c(1.0, 0.0)],
9090
[c(N, N), c(N, N), c(N, -0.0), c(N, 0.0), c(N, N), c(N, N), c(N, N)],

src/math.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ mod misc;
1010
mod trigonometric;
1111

1212
// Re-export from submodules
13-
pub use aggregate::{dist, fsum, prod, sumprod};
13+
pub use aggregate::{dist, fsum, prod, prod_int, sumprod, sumprod_int};
1414
pub use exponential::{cbrt, exp, exp2, expm1, log, log1p, log2, log10, pow, sqrt};
1515
pub use gamma::{erf, erfc, gamma, lgamma};
1616
pub use misc::{
17-
ceil, copysign, fabs, floor, fmod, frexp, isclose, isfinite, isinf, isnan, ldexp, modf,
17+
ceil, copysign, fabs, floor, fma, fmod, frexp, isclose, isfinite, isinf, isnan, ldexp, modf,
1818
nextafter, remainder, trunc, ulp,
1919
};
2020
pub use trigonometric::{

0 commit comments

Comments
 (0)