Files
IronCalc/base/src/functions/engineering/complex.rs
2025-06-29 11:07:05 +02:00

792 lines
26 KiB
Rust

use std::fmt;
use crate::{
calc_result::CalcResult,
expressions::{
lexer::util::get_tokens,
parser::Node,
token::{Error, OpSum, TokenType},
types::CellReferenceIndex,
},
model::Model,
number_format::to_precision,
};
/// This implements all functions with complex arguments in the standard
/// NOTE: If performance is ever needed we should have a new entry in CalcResult,
/// So this functions will return CalcResult::Complex(x,y, Suffix)
/// and not having to parse it over and over again.
#[derive(PartialEq, Debug)]
enum Suffix {
I,
J,
}
impl fmt::Display for Suffix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Suffix::I => write!(f, "i"),
Suffix::J => write!(f, "j"),
}
}
}
struct Complex {
x: f64,
y: f64,
suffix: Suffix,
}
impl fmt::Display for Complex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let x = to_precision(self.x, 15);
let y = to_precision(self.y, 15);
let suffix = &self.suffix;
// it is a bit weird what Excel does but it seems it uses general notation for
// numbers > 1e-20 and scientific notation for the rest
let y_str = if y.abs() <= 9e-20 {
format!("{y:E}")
} else if y == 1.0 {
"".to_string()
} else if y == -1.0 {
"-".to_string()
} else {
format!("{y}")
};
let x_str = if x.abs() <= 9e-20 {
format!("{x:E}")
} else {
format!("{x}")
};
if y == 0.0 && x == 0.0 {
write!(f, "0")
} else if y == 0.0 {
write!(f, "{x_str}")
} else if x == 0.0 {
write!(f, "{y_str}{suffix}")
} else if y > 0.0 {
write!(f, "{x_str}+{y_str}{suffix}")
} else {
write!(f, "{x_str}{y_str}{suffix}")
}
}
}
fn parse_complex_number(s: &str) -> Result<(f64, f64, Suffix), String> {
// Check for i, j, -i, -j
let (sign, s) = match s.strip_prefix('-') {
Some(r) => (-1.0, r),
None => (1.0, s),
};
match s {
"i" => return Ok((0.0, sign * 1.0, Suffix::I)),
"j" => return Ok((0.0, sign * 1.0, Suffix::J)),
_ => {
// Let it go
}
};
// TODO: This is an overuse
let tokens = get_tokens(s);
// There has to be 1, 2 3, or 4 tokens
// number
// number suffix
// number1+suffix
// number1+number2 suffix
match tokens.len() {
1 => {
// Real number
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
// i is the default
Ok((sign * number1, 0.0, Suffix::I))
}
2 => {
// number2 i
let number2 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[1].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
Ok((0.0, sign * number2, suffix))
}
3 => {
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let operation = match &tokens[1].token {
TokenType::Addition(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[2].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
let number2 = if matches!(operation, OpSum::Minus) {
-1.0
} else {
1.0
};
Ok((sign * number1, number2, suffix))
}
4 => {
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let operation = match &tokens[1].token {
TokenType::Addition(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let mut number2 = match tokens[2].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[3].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
if matches!(operation, OpSum::Minus) {
number2 = -number2
}
Ok((sign * number1, number2, suffix))
}
_ => Err(format!("Not a complex number: {s}")),
}
}
impl Model {
fn get_complex_number(
&mut self,
node: &Node,
cell: CellReferenceIndex,
) -> Result<(f64, f64, Suffix), CalcResult> {
let value = self.get_string(node, cell)?;
if value.is_empty() {
return Ok((0.0, 0.0, Suffix::I));
}
match parse_complex_number(&value) {
Ok(s) => Ok(s),
Err(message) => Err(CalcResult::new_error(Error::NUM, cell, message)),
}
}
// COMPLEX(real_num, i_num, [suffix])
pub(crate) fn fn_complex(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if !(2..=3).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let y = match self.get_number(&args[1], cell) {
Ok(s) => s,
Err(s) => return s,
};
let suffix = if args.len() == 3 {
match self.get_string(&args[2], cell) {
Ok(s) => {
if s == "i" || s.is_empty() {
Suffix::I
} else if s == "j" {
Suffix::J
} else {
return CalcResult::new_error(
Error::VALUE,
cell,
"Invalid suffix".to_string(),
);
}
}
Err(s) => return s,
}
} else {
Suffix::I
};
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imabs(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(f64::sqrt(x * x + y * y))
}
pub(crate) fn fn_imaginary(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (_, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(y)
}
pub(crate) fn fn_imargument(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
if x == 0.0 && y == 0.0 {
return CalcResult::new_error(Error::DIV, cell, "Division by zero".to_string());
}
let angle = f64::atan2(y, x);
CalcResult::Number(angle)
}
pub(crate) fn fn_imconjugate(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex { x, y: -y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcos(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let complex = Complex {
x: x.cos() * y.cosh(),
y: -x.sin() * y.sinh(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcosh(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let complex = Complex {
x: x.cosh() * y.cos(),
y: x.sinh() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcot(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if x == 0.0 && y != 0.0 {
let complex = Complex {
x: 0.0,
y: -1.0 / y.tanh(),
suffix,
};
return CalcResult::String(complex.to_string());
} else if y == 0.0 {
let complex = Complex {
x: 1.0 / x.tan(),
y: 0.0,
suffix,
};
return CalcResult::String(complex.to_string());
}
let x_cot = 1.0 / x.tan();
let y_coth = 1.0 / y.tanh();
let t = x_cot * x_cot + y_coth * y_coth;
let x = (x_cot * y_coth * y_coth - x_cot) / t;
let y = (-x_cot * x_cot * y_coth - y_coth) / t;
if x.is_infinite() || y.is_infinite() || x.is_nan() || y.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid operation".to_string());
}
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcsc(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let x_cos = x.cos();
let x_sin = x.sin();
let y_cosh = y.cosh();
let y_sinh = y.sinh();
let t = x_sin * x_sin * y_cosh * y_cosh + x_cos * x_cos * y_sinh * y_sinh;
let complex = Complex {
x: x_sin * y_cosh / t,
y: -x_cos * y_sinh / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcsch(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let x_cosh = x.cosh();
let x_sinh = x.sinh();
let y_cos = y.cos();
let y_sin = y.sin();
let t = x_sinh * x_sinh * y_cos * y_cos + x_cosh * x_cosh * y_sin * y_sin;
let complex = Complex {
x: x_sinh * y_cos / t,
y: -x_cosh * y_sin / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imdiv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let t = x2 * x2 + y2 * y2;
if t == 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid".to_string());
}
let complex = Complex {
x: (x1 * x2 + y1 * y2) / t,
y: (-x1 * y2 + y1 * x2) / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imexp(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.exp() * y.cos(),
y: x.exp() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imln(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.ln(),
y: a,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imlog10(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.log10(),
y: a * f64::log10(f64::exp(1.0)),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imlog2(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.log2(),
y: a * f64::log2(f64::exp(1.0)),
suffix,
};
CalcResult::String(complex.to_string())
}
// IMPOWER(imnumber, power)
// If $(r, \theta)$ is the polar representation the formula is:
// $$ x = r^n*\cos(n\dot\theta), y = r^n*\csin(n\dot\theta) $
pub(crate) fn fn_impower(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
// if n == n.trunc() && n < 10.0 {
// // for small powers we compute manually
// let (mut x0, mut y0) = (x, y);
// for _ in 1..(n.trunc() as i32) {
// (x0, y0) = (x0 * x - y0 * y, x0 * y + y0 * x);
// }
// let complex = Complex {
// x: x0,
// y: y0,
// suffix,
// };
// return CalcResult::String(complex.to_string());
// };
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let x = r.powf(n) * f64::cos(a * n);
let y = r.powf(n) * f64::sin(a * n);
if x.is_infinite() || y.is_infinite() || x.is_nan() || y.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid operation".to_string());
}
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_improduct(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 * x2 - y1 * y2,
y: x1 * y2 + y1 * x2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imreal(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, _, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(x)
}
pub(crate) fn fn_imsec(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_cos = x.cos();
let x_sin = x.sin();
let y_cosh = y.cosh();
let y_sinh = y.sinh();
let t = x_cos * x_cos * y_cosh * y_cosh + x_sin * x_sin * y_sinh * y_sinh;
let complex = Complex {
x: x_cos * y_cosh / t,
y: x_sin * y_sinh / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsech(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_cosh = x.cosh();
let x_sinh = x.sinh();
let y_cos = y.cos();
let y_sin = y.sin();
let t = x_cosh * x_cosh * y_cos * y_cos + x_sinh * x_sinh * y_sin * y_sin;
let complex = Complex {
x: x_cosh * y_cos / t,
y: -x_sinh * y_sin / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsin(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.sin() * y.cosh(),
y: x.cos() * y.sinh(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsinh(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.sinh() * y.cos(),
y: x.cosh() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsqrt(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y).sqrt();
let a = f64::atan2(y, x);
let complex = Complex {
x: r * f64::cos(a / 2.0),
y: r * f64::sin(a / 2.0),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsub(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 - x2,
y: y1 - y2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsum(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 + x2,
y: y1 + y2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imtan(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_tan = x.tan();
let y_tanh = y.tanh();
let t = 1.0 + x_tan * x_tan * y_tanh * y_tanh;
let complex = Complex {
x: (x_tan - x_tan * y_tanh * y_tanh) / t,
y: (y_tanh + x_tan * x_tan * y_tanh) / t,
suffix,
};
CalcResult::String(complex.to_string())
}
}
#[cfg(test)]
mod tests {
use crate::functions::engineering::complex::Suffix;
use super::parse_complex_number as parse;
#[test]
fn test_parse_complex() {
assert_eq!(parse("1+2i"), Ok((1.0, 2.0, Suffix::I)));
assert_eq!(parse("2i"), Ok((0.0, 2.0, Suffix::I)));
assert_eq!(parse("7.5"), Ok((7.5, 0.0, Suffix::I)));
assert_eq!(parse("-7.5"), Ok((-7.5, 0.0, Suffix::I)));
assert_eq!(parse("7-5i"), Ok((7.0, -5.0, Suffix::I)));
assert_eq!(parse("i"), Ok((0.0, 1.0, Suffix::I)));
assert_eq!(parse("7+i"), Ok((7.0, 1.0, Suffix::I)));
assert_eq!(parse("7-i"), Ok((7.0, -1.0, Suffix::I)));
assert_eq!(parse("-i"), Ok((0.0, -1.0, Suffix::I)));
assert_eq!(parse("0"), Ok((0.0, 0.0, Suffix::I)));
}
}