@@ -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]
172211pub 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]
0 commit comments