use crate::eval::coercion::{to_number, to_string_val};
use crate::eval::functions::check_arity;
use crate::types::{ErrorKind, Value};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct Complex {
pub re: f64,
pub im: f64,
}
impl Complex {
fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
fn abs(self) -> f64 {
libm::sqrt(self.re * self.re + self.im * self.im)
}
fn arg(self) -> f64 {
libm::atan2(self.im, self.re)
}
fn mul(self, rhs: Self) -> Self {
Self {
re: self.re * rhs.re - self.im * rhs.im,
im: self.re * rhs.im + self.im * rhs.re,
}
}
fn pow(self, n: Self) -> Option<Self> {
let r = self.abs();
if r == 0.0 {
if n.re == 0.0 && n.im == 0.0 {
return Some(Complex::new(1.0, 0.0));
}
if n.re > 0.0 {
return Some(Complex::new(0.0, 0.0));
}
return None; }
let theta = self.arg();
let ln_r = libm::log(r);
let exp_re = n.re * ln_r - n.im * theta;
let exp_im = n.im * ln_r + n.re * theta;
let scale = libm::exp(exp_re);
Some(Complex::new(scale * libm::cos(exp_im), scale * libm::sin(exp_im)))
}
fn sqrt(self) -> Self {
let r = self.abs();
if r == 0.0 {
return Complex::new(0.0, 0.0);
}
if self.im == 0.0 && self.re < 0.0 {
let theta = self.arg(); let sqrt_r = libm::sqrt(r);
return Complex::new(sqrt_r * libm::cos(theta / 2.0), sqrt_r * libm::sin(theta / 2.0));
}
let re_out = libm::sqrt((r + self.re) / 2.0);
let im_out = libm::sqrt((r - self.re) / 2.0);
let im_out = if self.im < 0.0 { -im_out } else { im_out };
Complex::new(re_out, im_out)
}
fn ln(self) -> Option<Self> {
let r = self.abs();
if r == 0.0 {
return None;
}
Some(Complex::new(libm::log(r), self.arg()))
}
}
pub(super) fn parse_complex(s: &str) -> Option<Complex> {
let s = s.trim();
if s.is_empty() {
return None;
}
let suffix = if s.ends_with('i') || s.ends_with('j') {
Some(s.chars().last().unwrap())
} else {
None
};
if suffix.is_none() {
let re = s.parse::<f64>().ok()?;
return Some(Complex::new(re, 0.0));
}
let s = &s[..s.len() - 1];
if s.is_empty() || s == "+" {
return Some(Complex::new(0.0, 1.0));
}
if s == "-" {
return Some(Complex::new(0.0, -1.0));
}
if !s.contains('+') && !s.contains('-') || s.starts_with('-') && s[1..].find(['+', '-']).is_none() {
let im = s.parse::<f64>().ok()?;
return Some(Complex::new(0.0, im));
}
let bytes = s.as_bytes();
let mut split = None;
let start = if bytes[0] == b'-' || bytes[0] == b'+' { 1 } else { 0 };
for i in (start + 1..bytes.len()).rev() {
if bytes[i] == b'+' || bytes[i] == b'-' {
split = Some(i);
break;
}
}
if let Some(idx) = split {
let mut re_str = &s[..idx];
let im_str = &s[idx..];
if !re_str.is_empty()
&& (re_str.ends_with('+') || re_str.ends_with('-'))
&& (re_str[..re_str.len()-1].parse::<f64>().is_ok() || re_str.len() == 1)
{
re_str = &re_str[..re_str.len()-1];
}
let re = if re_str.is_empty() { 0.0 } else { re_str.parse::<f64>().ok()? };
let im = if im_str == "+" || im_str.is_empty() {
1.0
} else if im_str == "-" {
-1.0
} else {
im_str.parse::<f64>().ok()?
};
Some(Complex::new(re, im))
} else {
let im = s.parse::<f64>().ok()?;
Some(Complex::new(0.0, im))
}
}
pub(super) fn format_complex(c: Complex, suffix: char) -> Value {
let re = c.re;
let im = c.im;
if im == 0.0 {
return Value::Text(format_num(re));
}
let re_str = if re == 0.0 {
String::new()
} else {
format_num(re)
};
let im_str = if im == 1.0 {
suffix.to_string()
} else if im == -1.0 {
format!("-{}", suffix)
} else {
format!("{}{}", format_num(im), suffix)
};
let result = if re == 0.0 {
im_str
} else if im > 0.0 {
format!("{}+{}", re_str, im_str)
} else {
format!("{}{}", re_str, im_str)
};
Value::Text(result)
}
fn format_num(n: f64) -> String {
if n == 0.0 {
return "0".to_string();
}
if n.fract() == 0.0 && n.abs() < 1e15 {
return format!("{}", n as i64);
}
let abs = n.abs();
if !(1e-9..1e15).contains(&abs) {
let s = format!("{:.14e}", n);
let (mantissa, exp_part) = s.split_once('e').unwrap();
let exp_num: i32 = exp_part.parse().unwrap();
let mantissa = mantissa.trim_end_matches('0').trim_end_matches('.');
format!("{}E{:+03}", mantissa, exp_num)
} else {
let s = format!("{:.14e}", n);
let (mantissa, exp_part) = s.split_once('e').unwrap();
let exp_num: i32 = exp_part.parse().unwrap();
let mantissa_stripped = mantissa.trim_end_matches('0').trim_end_matches('.');
let sign = if n < 0.0 { "-" } else { "" };
let mantissa_abs = mantissa_stripped.trim_start_matches('-');
let digits: String = mantissa_abs.chars().filter(|c| *c != '.').collect();
if exp_num < 0 {
let leading_zeros = (-exp_num - 1) as usize;
format!("{}0.{}{}", sign, "0".repeat(leading_zeros), digits)
} else {
let int_part_len = (exp_num + 1) as usize;
if int_part_len >= digits.len() {
format!(
"{}{}{}",
sign,
digits,
"0".repeat(int_part_len - digits.len())
)
} else {
let (int_part, frac_part) = digits.split_at(int_part_len);
format!("{}{}.{}", sign, int_part, frac_part)
}
}
}
}
fn value_to_complex(v: Value) -> Result<Complex, Value> {
match v {
Value::Number(n) | Value::Date(n) => Ok(Complex::new(n, 0.0)),
Value::Text(s) => {
parse_complex(&s).ok_or(Value::Error(ErrorKind::Num))
}
Value::Error(_) => Err(v),
Value::Bool(_) => Err(Value::Error(ErrorKind::Num)),
_ => {
match to_number(v) {
Ok(n) => Ok(Complex::new(n, 0.0)),
Err(e) => Err(e),
}
}
}
}
fn get_suffix(v: &Value) -> char {
if let Value::Text(s) = v {
if s.ends_with('j') { return 'j'; }
}
'i'
}
fn determine_suffix(args: &[Value]) -> Result<char, Value> {
let mut found: Option<char> = None;
for arg in args {
let s = match arg {
Value::Text(s) => s,
_ => continue,
};
let suffix = if s.ends_with('i') {
'i'
} else if s.ends_with('j') {
'j'
} else {
continue; };
match found {
None => found = Some(suffix),
Some(existing) if existing != suffix => {
return Err(Value::Error(ErrorKind::Num));
}
_ => {}
}
}
Ok(found.unwrap_or('i'))
}
pub fn complex_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 3) {
return err;
}
let re = match to_number(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let im = match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let suffix = if args.len() == 3 {
match to_string_val(args[2].clone()) {
Err(e) => return e,
Ok(s) => {
if s == "i" || s == "j" {
s.chars().next().unwrap()
} else {
return Value::Error(ErrorKind::Value);
}
}
}
} else {
'i'
};
format_complex(Complex::new(re, im), suffix)
}
pub fn imreal_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => Value::Number(c.re),
}
}
pub fn imaginary_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => Value::Number(c.im),
}
}
pub fn imabs_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => Value::Number(c.abs()),
}
}
pub fn improduct_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, usize::MAX) {
return err;
}
let suffix = match determine_suffix(args) {
Err(e) => return e,
Ok(s) => s,
};
let mut result = Complex::new(1.0, 0.0);
for arg in args {
match value_to_complex(arg.clone()) {
Err(e) => return e,
Ok(c) => result = result.mul(c),
}
}
format_complex(result, suffix)
}
pub fn imsub_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let suffix = match determine_suffix(args) {
Err(e) => return e,
Ok(s) => s,
};
let a = match value_to_complex(args[0].clone()) {
Err(e) => return e,
Ok(c) => c,
};
let b = match value_to_complex(args[1].clone()) {
Err(e) => return e,
Ok(c) => c,
};
format_complex(Complex::new(a.re - b.re, a.im - b.im), suffix)
}
pub fn imsum_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, usize::MAX) {
return err;
}
let suffix = match determine_suffix(args) {
Err(e) => return e,
Ok(s) => s,
};
let mut re = 0.0f64;
let mut im = 0.0f64;
for arg in args {
match value_to_complex(arg.clone()) {
Err(e) => return e,
Ok(c) => {
re += c.re;
im += c.im;
}
}
}
format_complex(Complex::new(re, im), suffix)
}
pub fn imdiv_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let suffix = match determine_suffix(args) {
Err(e) => return e,
Ok(s) => s,
};
let a = match value_to_complex(args[0].clone()) {
Err(e) => return e,
Ok(c) => c,
};
let b = match value_to_complex(args[1].clone()) {
Err(e) => return e,
Ok(c) => c,
};
let denom = b.re * b.re + b.im * b.im;
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = (a.re * b.re + a.im * b.im) / denom;
let im = (a.im * b.re - a.re * b.im) / denom;
format_complex(Complex::new(re, im), suffix)
}
pub fn imconjugate_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => format_complex(Complex::new(c.re, -c.im), suffix),
}
}
pub fn imargument_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
if c.re == 0.0 && c.im == 0.0 {
Value::Error(ErrorKind::DivByZero)
} else {
Value::Number(c.arg())
}
}
}
}
pub fn imln_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => match c.ln() {
None => Value::Error(ErrorKind::DivByZero),
Some(result) => format_complex(result, 'i'),
},
}
}
pub fn imlog10_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => match c.ln() {
None => Value::Error(ErrorKind::DivByZero),
Some(result) => {
let ln10 = libm::log(10.0f64);
format_complex(Complex::new(result.re / ln10, result.im / ln10), 'i')
}
},
}
}
pub fn imlog2_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => match c.ln() {
None => Value::Error(ErrorKind::DivByZero),
Some(result) => {
let ln2 = libm::log(2.0f64);
format_complex(Complex::new(result.re / ln2, result.im / ln2), 'i')
}
},
}
}
pub fn imlog_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let c = match value_to_complex(args[0].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let base = match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
};
if base <= 0.0 || base == 1.0 {
return Value::Error(ErrorKind::Num);
}
match c.ln() {
None => Value::Error(ErrorKind::DivByZero),
Some(result) => {
let ln_base = libm::log(base);
format_complex(Complex::new(result.re / ln_base, result.im / ln_base), 'i')
}
}
}
pub fn imexp_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let scale = libm::exp(c.re);
format_complex(Complex::new(scale * libm::cos(c.im), scale * libm::sin(c.im)), suffix)
}
}
}
pub fn impower_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 2) {
return err;
}
let base = match value_to_complex(args[0].clone()) {
Err(e) => return e,
Ok(c) => c,
};
let exp_n = match to_number(args[1].clone()) {
Err(e) => return e,
Ok(v) => v,
};
let exp = Complex::new(exp_n, 0.0);
let suffix = get_suffix(&args[0]);
match base.pow(exp) {
None => Value::Error(ErrorKind::Num),
Some(mut result) => {
if result.re.round() != 0.0
&& (result.re.round() - result.re).abs() < 1e-9 * result.re.abs().max(1.0)
{
result.re = result.re.round();
}
if result.im.round() != 0.0
&& (result.im.round() - result.im).abs() < 1e-9 * result.im.abs().max(1.0)
{
result.im = result.im.round();
}
format_complex(result, suffix)
}
}
}
pub fn imsqrt_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => format_complex(c.sqrt(), suffix),
}
}
pub fn imsin_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let re = libm::sin(c.re) * libm::cosh(c.im);
let im = libm::cos(c.re) * libm::sinh(c.im);
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imcos_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let re = libm::cos(c.re) * libm::cosh(c.im);
let im = -(libm::sin(c.re) * libm::sinh(c.im));
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imtan_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let a2 = 2.0 * c.re;
let b2 = 2.0 * c.im;
let denom = libm::cos(a2) + libm::cosh(b2);
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = libm::sin(a2) / denom;
let im = libm::sinh(b2) / denom;
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imcot_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let a2 = 2.0 * c.re;
let b2 = 2.0 * c.im;
let denom = libm::cosh(b2) - libm::cos(a2);
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = libm::sin(a2) / denom;
let im = -(libm::sinh(b2) / denom);
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imcsc_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let sin_re = libm::sin(c.re) * libm::cosh(c.im);
let sin_im = libm::cos(c.re) * libm::sinh(c.im);
let denom = sin_re * sin_re + sin_im * sin_im;
if denom == 0.0 {
return Value::Error(ErrorKind::Num);
}
let re = sin_re / denom;
let im = -sin_im / denom;
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imsec_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let cos_re = libm::cos(c.re) * libm::cosh(c.im);
let cos_im = -(libm::sin(c.re) * libm::sinh(c.im));
let denom = cos_re * cos_re + cos_im * cos_im;
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = cos_re / denom;
let im = -cos_im / denom;
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imsinh_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let re = libm::sinh(c.re) * libm::cos(c.im);
let im = libm::cosh(c.re) * libm::sin(c.im);
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imcosh_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let re = libm::cosh(c.re) * libm::cos(c.im);
let im = libm::sinh(c.re) * libm::sin(c.im);
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imtanh_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let a2 = 2.0 * c.re;
let b2 = 2.0 * c.im;
let denom = libm::cosh(a2) + libm::cos(b2);
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = libm::sinh(a2) / denom;
let im = libm::sin(b2) / denom;
if im == 0.0 {
return Value::Text(format_num(re));
}
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imcoth_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let a2 = 2.0 * c.re;
let b2 = 2.0 * c.im;
let denom = libm::cosh(a2) - libm::cos(b2);
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = libm::sinh(a2) / denom;
let im = -(libm::sin(b2) / denom);
if im == 0.0 {
return Value::Text(format_num(re));
}
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imcsch_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let sinh_re = libm::sinh(c.re) * libm::cos(c.im);
let sinh_im = libm::cosh(c.re) * libm::sin(c.im);
let denom = sinh_re * sinh_re + sinh_im * sinh_im;
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = sinh_re / denom;
let im = -sinh_im / denom;
format_complex(Complex::new(re, im), suffix)
}
}
}
pub fn imsech_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let suffix = get_suffix(&args[0]);
match value_to_complex(args[0].clone()) {
Err(e) => e,
Ok(c) => {
let cosh_re = libm::cosh(c.re) * libm::cos(c.im);
let cosh_im = libm::sinh(c.re) * libm::sin(c.im);
let denom = cosh_re * cosh_re + cosh_im * cosh_im;
if denom == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
let re = cosh_re / denom;
let im = -cosh_im / denom;
format_complex(Complex::new(re, im), suffix)
}
}
}
#[cfg(test)]
mod tests;