diff --git a/complex/src/lib.rs b/complex/src/lib.rs index f16a7e1..334f8e0 100644 --- a/complex/src/lib.rs +++ b/complex/src/lib.rs @@ -22,10 +22,12 @@ extern crate rustc_serialize; #[cfg(feature = "serde")] extern crate serde; +use std::error::Error; use std::fmt; #[cfg(test)] use std::hash; use std::ops::{Add, Div, Mul, Neg, Sub}; +use std::str::FromStr; use traits::{Zero, One, Num, Float}; @@ -178,7 +180,7 @@ impl Complex { let (r, theta) = self.to_polar(); Complex::from_polar(&(r.sqrt()), &(theta/two)) } - + /// Raises `self` to a floating point power. #[inline] pub fn powf(&self, exp: T) -> Complex { @@ -187,25 +189,25 @@ impl Complex { let (r, theta) = self.to_polar(); Complex::from_polar(&r.powf(exp), &(theta*exp)) } - + /// Returns the logarithm of `self` with respect to an arbitrary base. #[inline] pub fn log(&self, base: T) -> Complex { - // formula: log_y(x) = log_y(ρ e^(i θ)) - // = log_y(ρ) + log_y(e^(i θ)) = log_y(ρ) + ln(e^(i θ)) / ln(y) - // = log_y(ρ) + i θ / ln(y) + // formula: log_y(x) = log_y(ρ e^(i θ)) + // = log_y(ρ) + log_y(e^(i θ)) = log_y(ρ) + ln(e^(i θ)) / ln(y) + // = log_y(ρ) + i θ / ln(y) let (r, theta) = self.to_polar(); Complex::new(r.log(base), theta / base.ln()) } - + /// Raises `self` to a complex power. #[inline] pub fn powc(&self, exp: Complex) -> Complex { // formula: x^y = (a + i b)^(c + i d) - // = (ρ e^(i θ))^c (ρ e^(i θ))^(i d) + // = (ρ e^(i θ))^c (ρ e^(i θ))^(i d) // where ρ=|x| and θ=arg(x) // = ρ^c e^(−d θ) e^(i c θ) ρ^(i d) - // = p^c e^(−d θ) (cos(c θ) + // = p^c e^(−d θ) (cos(c θ) // + i sin(c θ)) (cos(d ln(ρ)) + i sin(d ln(ρ))) // = p^c e^(−d θ) ( // cos(c θ) cos(d ln(ρ)) − sin(c θ) sin(d ln(ρ)) @@ -214,14 +216,14 @@ impl Complex { // = from_polar(p^c e^(−d θ), c θ + d ln(ρ)) let (r, theta) = self.to_polar(); Complex::from_polar( - &(r.powf(exp.re) * (-exp.im * theta).exp()), + &(r.powf(exp.re) * (-exp.im * theta).exp()), &(exp.re * theta + exp.im * r.ln())) } - + /// Raises a floating point number to the complex power `self`. #[inline] pub fn expf(&self, base: T) -> Complex { - // formula: x^(a+bi) = x^a x^bi = x^a e^(b ln(x) i) + // formula: x^(a+bi) = x^a x^bi = x^a e^(b ln(x) i) // = from_polar(x^a, b ln(x)) Complex::from_polar(&base.powf(self.re), &(self.im * base.ln())) } @@ -877,6 +879,97 @@ impl fmt::Binary for Complex where } } +impl FromStr for Complex where + T: FromStr + Num + Clone +{ + type Err = ParseComplexError; + + /// Parses `a +/- bi`; `ai +/- b`; `a`; or `bi` where `a` and `b` are of type `T` + fn from_str(s: &str) -> Result + { + let imag = match s.rfind('j') { + None => 'i', + _ => 'j' + }; + + let mut b = String::with_capacity(s.len()); + let mut first = true; + + let char_indices = s.char_indices(); + let mut pc = ' '; + let mut split_index = s.len(); + + for (i, cc) in char_indices { + if cc == '+' && pc != 'e' && pc != 'E' && i > 0 { + // ignore '+' if part of an exponent + if first { + split_index = i; + first = false; + } + // don't carry '+' over into b + pc = ' '; + continue; + } else if cc == '-' && pc != 'e' && pc != 'E' && i > 0 { + // ignore '-' if part of an exponent or begins the string + if first { + split_index = i; + first = false; + } + // DO carry '-' over into b + } + + if pc == '-' && cc == ' ' && !first { + // ignore whitespace between minus sign and next number + continue; + } + + if !first { + b.push(cc); + } + pc = cc; + } + + // split off real and imaginary parts, trim whitespace + let (a, _) = s.split_at(split_index); + let a = a.trim_right(); + let mut b = b.trim_left(); + // input was either pure real or pure imaginary + if b.is_empty() { + b = match a.ends_with(imag) { + false => "0i", + true => "0" + }; + } + + let re; + let im; + if a.ends_with(imag) { + im = a; re = b; + } else if b.ends_with(imag) { + re = a; im = b; + } else { + return Err(ParseComplexError::new()); + } + + // parse re + let re = try!(T::from_str(re).map_err(ParseComplexError::from_error)); + + // pop imaginary unit off + let mut im = &im[..im.len()-1]; + // handle im == "i" or im == "-i" + if im.is_empty() || im == "+" { + im = "1"; + } else if im == "-" { + im = "-1"; + } + + // parse im + let im = try!(T::from_str(im).map_err(ParseComplexError::from_error)); + + Ok(Complex::new(re, im)) + } +} + #[cfg(feature = "serde")] impl serde::Serialize for Complex where T: serde::Serialize @@ -900,6 +993,51 @@ impl serde::Deserialize for Complex where } } +#[derive(Debug, PartialEq)] +pub struct ParseComplexError +{ + kind: ComplexErrorKind, +} + +#[derive(Debug, PartialEq)] +enum ComplexErrorKind +{ + ParseError(E), + ExprError +} + +impl ParseComplexError +{ + fn new() -> Self { + ParseComplexError { + kind: ComplexErrorKind::ExprError, + } + } + + fn from_error(error: E) -> Self { + ParseComplexError { + kind: ComplexErrorKind::ParseError(error), + } + } +} + +impl Error for ParseComplexError +{ + fn description(&self) -> &str { + match self.kind { + ComplexErrorKind::ParseError(ref e) => e.description(), + ComplexErrorKind::ExprError => "invalid or unsupported complex expression" + } + } +} + +impl fmt::Display for ParseComplexError +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.description().fmt(f) + } +} + #[cfg(test)] fn hash(x: &T) -> u64 { use std::hash::{BuildHasher, Hasher}; @@ -915,6 +1053,7 @@ mod test { use super::{Complex64, Complex}; use std::f64; + use std::str::FromStr; use traits::{Zero, One, Float}; @@ -1018,7 +1157,7 @@ mod test { fn close(a: Complex64, b: Complex64) -> bool { close_to_tol(a, b, 1e-10) } - + fn close_to_tol(a: Complex64, b: Complex64, tol: f64) -> bool { // returns true if a and b are reasonably close (a == b) || (a-b).norm() < tol @@ -1052,7 +1191,7 @@ mod test { assert!(-f64::consts::PI <= c.ln().arg() && c.ln().arg() <= f64::consts::PI); } } - + #[test] fn test_powc() { @@ -1063,7 +1202,7 @@ mod test { let c = Complex::new(1.0 / 3.0, 0.1); assert!(close_to_tol(a.powc(c), Complex::new(1.65826, -0.33502), 1e-5)); } - + #[test] fn test_powf() { @@ -1071,7 +1210,7 @@ mod test { let r = c.powf(3.5); assert!(close_to_tol(r, Complex::new(-0.8684746, -16.695934), 1e-5)); } - + #[test] fn test_log() { @@ -1079,18 +1218,18 @@ mod test { let r = c.log(10.0); assert!(close_to_tol(r, Complex::new(0.349485, -0.20135958), 1e-5)); } - + #[test] fn test_some_expf_cases() { let c = Complex::new(2.0, -1.0); let r = c.expf(10.0); assert!(close_to_tol(r, Complex::new(-66.82015, -74.39803), 1e-5)); - + let c = Complex::new(5.0, -2.0); let r = c.expf(3.4); assert!(close_to_tol(r, Complex::new(-349.25, -290.63), 1e-2)); - + let c = Complex::new(-1.5, 2.0 / 3.0); let r = c.expf(1.0 / 3.0); assert!(close_to_tol(r, Complex::new(3.8637, -3.4745), 1e-2)); @@ -1566,4 +1705,80 @@ mod test { assert!(!b.is_normal()); assert!(_1_1i.is_normal()); } + + #[test] + fn test_from_str() { + fn test(z: Complex64, s: &str) { + assert_eq!(FromStr::from_str(s), Ok(z)); + } + test(_0_0i, "0 + 0i"); + test(_0_0i, "0+0j"); + test(_0_0i, "0 - 0j"); + test(_0_0i, "0-0i"); + test(_0_0i, "0i + 0"); + test(_0_0i, "0"); + test(_0_0i, "-0"); + test(_0_0i, "0i"); + test(_0_0i, "0j"); + test(_0_0i, "+0j"); + test(_0_0i, "-0i"); + + test(_1_0i, "1 + 0i"); + test(_1_0i, "1+0j"); + test(_1_0i, "1 - 0j"); + test(_1_0i, "+1-0i"); + test(_1_0i, "-0j+1"); + test(_1_0i, "1"); + + test(_1_1i, "1 + i"); + test(_1_1i, "1+j"); + test(_1_1i, "1 + 1j"); + test(_1_1i, "1+1i"); + test(_1_1i, "i + 1"); + test(_1_1i, "1i+1"); + test(_1_1i, "+j+1"); + + test(_0_1i, "0 + i"); + test(_0_1i, "0+j"); + test(_0_1i, "-0 + j"); + test(_0_1i, "-0+i"); + test(_0_1i, "0 + 1i"); + test(_0_1i, "0+1j"); + test(_0_1i, "-0 + 1j"); + test(_0_1i, "-0+1i"); + test(_0_1i, "j + 0"); + test(_0_1i, "i"); + test(_0_1i, "j"); + test(_0_1i, "1j"); + + test(_neg1_1i, "-1 + i"); + test(_neg1_1i, "-1+j"); + test(_neg1_1i, "-1 + 1j"); + test(_neg1_1i, "-1+1i"); + test(_neg1_1i, "1i-1"); + test(_neg1_1i, "j + -1"); + + test(_05_05i, "0.5 + 0.5i"); + test(_05_05i, "0.5+0.5j"); + test(_05_05i, "5e-1+0.5j"); + test(_05_05i, "5E-1 + 0.5j"); + test(_05_05i, "5E-1i + 0.5"); + test(_05_05i, "0.05e+1j + 50E-2"); + } + + #[test] + fn test_from_str_fail() { + fn test(s: &str) { + let complex: Result = FromStr::from_str(s); + assert!(complex.is_err()); + } + test("foo"); + test("6E"); + test("0 + 2.718"); + test("1 - -2i"); + test("314e-2ij"); + test("4.3j - i"); + test("1i - 2i"); + test("+ 1 - 3.0i"); + } }