use super::utils::{ARG_ANY_TWO, ARG_NUM_LENIENT_TWO, coerce_num};
use crate::args::ArgSchema;
use crate::function::Function;
use crate::traits::{ArgumentHandle, FunctionContext};
use formualizer_common::{ExcelError, LiteralValue};
use formualizer_macros::func_caps;
mod transcendental;
fn to_bitwise_int(v: &LiteralValue) -> Result<i64, ExcelError> {
let n = coerce_num(v)?;
if n < 0.0 || n != n.trunc() || n >= 281474976710656.0 {
return Err(ExcelError::new_num());
}
Ok(n as i64)
}
#[derive(Debug)]
pub struct BitAndFn;
impl Function for BitAndFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BITAND"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let a = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let b = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
(a & b) as f64,
)))
}
}
#[derive(Debug)]
pub struct BitOrFn;
impl Function for BitOrFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BITOR"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let a = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let b = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
(a | b) as f64,
)))
}
}
#[derive(Debug)]
pub struct BitXorFn;
impl Function for BitXorFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BITXOR"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let a = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let b = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
(a ^ b) as f64,
)))
}
}
#[derive(Debug)]
pub struct BitLShiftFn;
impl Function for BitLShiftFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BITLSHIFT"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let shift = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)? as i32,
};
let result = if shift >= 0 {
if shift >= 48 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let shifted = n << shift;
if shifted >= 281474976710656 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
shifted
} else {
let rshift = (-shift) as u32;
if rshift >= 48 { 0 } else { n >> rshift }
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result as f64,
)))
}
}
#[derive(Debug)]
pub struct BitRShiftFn;
impl Function for BitRShiftFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BITRSHIFT"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match to_bitwise_int(&other) {
Ok(n) => n,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let shift = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)? as i32,
};
let result = if shift >= 0 {
if shift >= 48 { 0 } else { n >> shift }
} else {
let lshift = (-shift) as u32;
if lshift >= 48 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let shifted = n << lshift;
if shifted >= 281474976710656 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
shifted
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result as f64,
)))
}
}
use super::utils::ARG_ANY_ONE;
fn coerce_base_text(v: &LiteralValue) -> Result<String, ExcelError> {
match v {
LiteralValue::Text(s) => Ok(s.clone()),
LiteralValue::Int(i) => Ok(i.to_string()),
LiteralValue::Number(n) => Ok((*n as i64).to_string()),
LiteralValue::Error(e) => Err(e.clone()),
_ => Err(ExcelError::new_value()),
}
}
#[derive(Debug)]
pub struct Bin2DecFn;
impl Function for Bin2DecFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BIN2DEC"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| c == '0' || c == '1') {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let result = if text.len() == 10 && text.starts_with('1') {
let val = i64::from_str_radix(&text, 2).unwrap_or(0);
val - 1024 } else {
i64::from_str_radix(&text, 2).unwrap_or(0)
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result as f64,
)))
}
}
#[derive(Debug)]
pub struct Dec2BinFn;
impl Function for Dec2BinFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"DEC2BIN"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)? as i64,
};
if !(-512..=511).contains(&n) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let binary = if n >= 0 {
format!("{:b}", n)
} else {
format!("{:010b}", (n + 1024) as u64)
};
let result = if let Some(p) = places {
if p < binary.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", binary, width = p)
} else {
binary
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Hex2DecFn;
impl Function for Hex2DecFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"HEX2DEC"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s.to_uppercase(),
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let result = if text.len() == 10 && text.starts_with(|c| c >= '8') {
let val = i64::from_str_radix(&text, 16).unwrap_or(0);
val - (1i64 << 40)
} else {
i64::from_str_radix(&text, 16).unwrap_or(0)
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result as f64,
)))
}
}
#[derive(Debug)]
pub struct Dec2HexFn;
impl Function for Dec2HexFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"DEC2HEX"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)? as i64,
};
if !(-(1i64 << 39)..=(1i64 << 39) - 1).contains(&n) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let hex = if n >= 0 {
format!("{:X}", n)
} else {
format!("{:010X}", (n + (1i64 << 40)) as u64)
};
let result = if let Some(p) = places {
if p < hex.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", hex, width = p)
} else {
hex
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Oct2DecFn;
impl Function for Oct2DecFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"OCT2DEC"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| ('0'..='7').contains(&c)) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let result = if text.len() == 10 && text.starts_with(|c| c >= '4') {
let val = i64::from_str_radix(&text, 8).unwrap_or(0);
val - (1i64 << 30)
} else {
i64::from_str_radix(&text, 8).unwrap_or(0)
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result as f64,
)))
}
}
#[derive(Debug)]
pub struct Dec2OctFn;
impl Function for Dec2OctFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"DEC2OCT"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)? as i64,
};
if !(-(1i64 << 29)..=(1i64 << 29) - 1).contains(&n) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let octal = if n >= 0 {
format!("{:o}", n)
} else {
format!("{:010o}", (n + (1i64 << 30)) as u64)
};
let result = if let Some(p) = places {
if p < octal.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", octal, width = p)
} else {
octal
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Bin2HexFn;
impl Function for Bin2HexFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BIN2HEX"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| c == '0' || c == '1') {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let dec = if text.len() == 10 && text.starts_with('1') {
let val = i64::from_str_radix(&text, 2).unwrap_or(0);
val - 1024
} else {
i64::from_str_radix(&text, 2).unwrap_or(0)
};
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let hex = if dec >= 0 {
format!("{:X}", dec)
} else {
format!("{:010X}", (dec + (1i64 << 40)) as u64)
};
let result = if let Some(p) = places {
if p < hex.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", hex, width = p)
} else {
hex
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Hex2BinFn;
impl Function for Hex2BinFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"HEX2BIN"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s.to_uppercase(),
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let dec = if text.len() == 10 && text.starts_with(|c| c >= '8') {
let val = i64::from_str_radix(&text, 16).unwrap_or(0);
val - (1i64 << 40)
} else {
i64::from_str_radix(&text, 16).unwrap_or(0)
};
if !(-512..=511).contains(&dec) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let binary = if dec >= 0 {
format!("{:b}", dec)
} else {
format!("{:010b}", (dec + 1024) as u64)
};
let result = if let Some(p) = places {
if p < binary.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", binary, width = p)
} else {
binary
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Bin2OctFn;
impl Function for Bin2OctFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BIN2OCT"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| c == '0' || c == '1') {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let dec = if text.len() == 10 && text.starts_with('1') {
let val = i64::from_str_radix(&text, 2).unwrap_or(0);
val - 1024
} else {
i64::from_str_radix(&text, 2).unwrap_or(0)
};
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let octal = if dec >= 0 {
format!("{:o}", dec)
} else {
format!("{:010o}", (dec + (1i64 << 30)) as u64)
};
let result = if let Some(p) = places {
if p < octal.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", octal, width = p)
} else {
octal
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Oct2BinFn;
impl Function for Oct2BinFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"OCT2BIN"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| ('0'..='7').contains(&c)) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let dec = if text.len() == 10 && text.starts_with(|c| c >= '4') {
let val = i64::from_str_radix(&text, 8).unwrap_or(0);
val - (1i64 << 30)
} else {
i64::from_str_radix(&text, 8).unwrap_or(0)
};
if !(-512..=511).contains(&dec) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let binary = if dec >= 0 {
format!("{:b}", dec)
} else {
format!("{:010b}", (dec + 1024) as u64)
};
let result = if let Some(p) = places {
if p < binary.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", binary, width = p)
} else {
binary
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Hex2OctFn;
impl Function for Hex2OctFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"HEX2OCT"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s.to_uppercase(),
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| c.is_ascii_hexdigit()) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let dec = if text.len() == 10 && text.starts_with(|c| c >= '8') {
let val = i64::from_str_radix(&text, 16).unwrap_or(0);
val - (1i64 << 40)
} else {
i64::from_str_radix(&text, 16).unwrap_or(0)
};
if !(-(1i64 << 29)..=(1i64 << 29) - 1).contains(&dec) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let octal = if dec >= 0 {
format!("{:o}", dec)
} else {
format!("{:010o}", (dec + (1i64 << 30)) as u64)
};
let result = if let Some(p) = places {
if p < octal.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", octal, width = p)
} else {
octal
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct Oct2HexFn;
impl Function for Oct2HexFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"OCT2HEX"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let text = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_base_text(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
if text.len() > 10 || !text.chars().all(|c| ('0'..='7').contains(&c)) {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let dec = if text.len() == 10 && text.starts_with(|c| c >= '4') {
let val = i64::from_str_radix(&text, 8).unwrap_or(0);
val - (1i64 << 30)
} else {
i64::from_str_radix(&text, 8).unwrap_or(0)
};
let places = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => Some(coerce_num(&other)? as usize),
}
} else {
None
};
let hex = if dec >= 0 {
format!("{:X}", dec)
} else {
format!("{:010X}", (dec + (1i64 << 40)) as u64)
};
let result = if let Some(p) = places {
if p < hex.len() || p > 10 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
format!("{:0>width$}", hex, width = p)
} else {
hex
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct DeltaFn;
impl Function for DeltaFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"DELTA"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n1 = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let n2 = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
}
} else {
0.0
};
let result = if (n1 - n2).abs() < 1e-12 { 1.0 } else { 0.0 };
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
)))
}
}
#[derive(Debug)]
pub struct GestepFn;
impl Function for GestepFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"GESTEP"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let n = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let step = if args.len() > 1 {
match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
}
} else {
0.0
};
let result = if n >= step { 1.0 } else { 0.0 };
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
)))
}
}
#[allow(clippy::excessive_precision)]
fn erf_approx(x: f64) -> f64 {
let ax = x.abs();
if ax < 0.5 {
const P: [f64; 5] = [
3.20937758913846947e+03,
3.77485237685302021e+02,
1.13864154151050156e+02,
3.16112374387056560e+00,
1.85777706184603153e-01,
];
const Q: [f64; 5] = [
2.84423748127893300e+03,
1.28261652607737228e+03,
2.44024637934444173e+02,
2.36012909523441209e+01,
1.00000000000000000e+00,
];
let x2 = x * x;
let p_val = P[4];
let p_val = p_val * x2 + P[3];
let p_val = p_val * x2 + P[2];
let p_val = p_val * x2 + P[1];
let p_val = p_val * x2 + P[0];
let q_val = Q[4];
let q_val = q_val * x2 + Q[3];
let q_val = q_val * x2 + Q[2];
let q_val = q_val * x2 + Q[1];
let q_val = q_val * x2 + Q[0];
return x * p_val / q_val;
}
if ax < 4.0 {
let erfc_val = erfc_mid(ax);
return if x > 0.0 {
1.0 - erfc_val
} else {
erfc_val - 1.0
};
}
let erfc_val = erfc_large(ax);
if x > 0.0 {
1.0 - erfc_val
} else {
erfc_val - 1.0
}
}
#[allow(clippy::excessive_precision)]
fn erfc_mid(x: f64) -> f64 {
const P: [f64; 9] = [
1.23033935479799725e+03,
2.05107837782607147e+03,
1.71204761263407058e+03,
8.81952221241769090e+02,
2.98635138197400131e+02,
6.61191906371416295e+01,
8.88314979438837594e+00,
5.64188496988670089e-01,
2.15311535474403846e-08,
];
const Q: [f64; 9] = [
1.23033935480374942e+03,
3.43936767414372164e+03,
4.36261909014324716e+03,
3.29079923573345963e+03,
1.62138957456669019e+03,
5.37181101862009858e+02,
1.17693950891312499e+02,
1.57449261107098347e+01,
1.00000000000000000e+00,
];
let p_val = P[8];
let p_val = p_val * x + P[7];
let p_val = p_val * x + P[6];
let p_val = p_val * x + P[5];
let p_val = p_val * x + P[4];
let p_val = p_val * x + P[3];
let p_val = p_val * x + P[2];
let p_val = p_val * x + P[1];
let p_val = p_val * x + P[0];
let q_val = Q[8];
let q_val = q_val * x + Q[7];
let q_val = q_val * x + Q[6];
let q_val = q_val * x + Q[5];
let q_val = q_val * x + Q[4];
let q_val = q_val * x + Q[3];
let q_val = q_val * x + Q[2];
let q_val = q_val * x + Q[1];
let q_val = q_val * x + Q[0];
(-x * x).exp() * p_val / q_val
}
#[allow(clippy::excessive_precision)]
fn erfc_large(x: f64) -> f64 {
const P: [f64; 6] = [
6.58749161529837803e-04,
1.60837851487422766e-02,
1.25781726111229246e-01,
3.60344899949804439e-01,
3.05326634961232344e-01,
1.63153871373020978e-02,
];
const Q: [f64; 6] = [
2.33520497626869185e-03,
6.05183413124413191e-02,
5.27905102951428412e-01,
1.87295284992346047e+00,
2.56852019228982242e+00,
1.00000000000000000e+00,
];
let x2 = x * x;
let inv_x2 = 1.0 / x2;
let p_val = P[5];
let p_val = p_val * inv_x2 + P[4];
let p_val = p_val * inv_x2 + P[3];
let p_val = p_val * inv_x2 + P[2];
let p_val = p_val * inv_x2 + P[1];
let p_val = p_val * inv_x2 + P[0];
let q_val = Q[5];
let q_val = q_val * inv_x2 + Q[4];
let q_val = q_val * inv_x2 + Q[3];
let q_val = q_val * inv_x2 + Q[2];
let q_val = q_val * inv_x2 + Q[1];
let q_val = q_val * inv_x2 + Q[0];
const FRAC_1_SQRT_PI: f64 = 0.5641895835477563;
(-x2).exp() / x * (FRAC_1_SQRT_PI + inv_x2 * p_val / q_val)
}
fn erfc_direct(x: f64) -> f64 {
if x < 0.0 {
return 2.0 - erfc_direct(-x);
}
if x < 0.5 {
return 1.0 - erf_approx(x);
}
if x < 4.0 {
return erfc_mid(x);
}
erfc_large(x)
}
#[derive(Debug)]
pub struct ErfFn;
impl Function for ErfFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ERF"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_NUM_LENIENT_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let lower = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let result = if args.len() > 1 {
let upper = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
erf_approx(upper) - erf_approx(lower)
} else {
erf_approx(lower)
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
)))
}
}
#[derive(Debug)]
pub struct ErfcFn;
impl Function for ErfcFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ERFC"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let x = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let result = erfc_direct(x);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
)))
}
}
#[derive(Debug)]
pub struct ErfcPreciseFn;
impl Function for ErfcPreciseFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ERFC.PRECISE"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let x = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
erfc_direct(x),
)))
}
}
fn eval_bessel<'b, F>(
args: &[ArgumentHandle<'_, 'b>],
f: F,
) -> Result<crate::traits::CalcValue<'b>, ExcelError>
where
F: FnOnce(i32, f64) -> f64,
{
if args.len() != 2 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let x = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let n = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
if !x.is_finite() || !n.is_finite() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let n_trunc = n.trunc();
if n_trunc < 0.0 || n_trunc > i32::MAX as f64 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let result = f(n_trunc as i32, x);
if !result.is_finite() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
)))
}
#[derive(Debug)]
pub struct BesselIFn;
impl Function for BesselIFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BESSELI"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_bessel(args, transcendental::bessel_i)
}
}
#[derive(Debug)]
pub struct BesselJFn;
impl Function for BesselJFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BESSELJ"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_bessel(args, transcendental::bessel_j)
}
}
#[derive(Debug)]
pub struct BesselKFn;
impl Function for BesselKFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BESSELK"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_bessel(args, transcendental::bessel_k)
}
}
#[derive(Debug)]
pub struct BesselYFn;
impl Function for BesselYFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"BESSELY"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_bessel(args, transcendental::bessel_y)
}
}
#[derive(Debug)]
pub struct ErfPreciseFn;
impl Function for ErfPreciseFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"ERF.PRECISE"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let x = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let result = erf_approx(x);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
)))
}
}
fn parse_complex(s: &str) -> Result<(f64, f64, char), ExcelError> {
let s = s.trim();
if s.is_empty() {
return Err(ExcelError::new_num());
}
let suffix = if s.ends_with('i') || s.ends_with('I') {
'i'
} else if s.ends_with('j') || s.ends_with('J') {
'j'
} else {
let real: f64 = s.parse().map_err(|_| ExcelError::new_num())?;
return Ok((real, 0.0, 'i'));
};
let s = &s[..s.len() - 1];
if s.is_empty() || s == "+" {
return Ok((0.0, 1.0, suffix));
}
if s == "-" {
return Ok((0.0, -1.0, suffix));
}
let mut split_pos = None;
let bytes = s.as_bytes();
for i in (1..bytes.len()).rev() {
let c = bytes[i] as char;
if c == '+' || c == '-' {
if i > 0 {
let prev = bytes[i - 1] as char;
if prev == 'e' || prev == 'E' {
continue;
}
}
split_pos = Some(i);
break;
}
}
match split_pos {
Some(pos) => {
let real_str = &s[..pos];
let imag_str = &s[pos..];
let real: f64 = if real_str.is_empty() {
0.0
} else {
real_str.parse().map_err(|_| ExcelError::new_num())?
};
let imag: f64 = if imag_str == "+" {
1.0
} else if imag_str == "-" {
-1.0
} else {
imag_str.parse().map_err(|_| ExcelError::new_num())?
};
Ok((real, imag, suffix))
}
None => {
let imag: f64 = s.parse().map_err(|_| ExcelError::new_num())?;
Ok((0.0, imag, suffix))
}
}
}
fn clean_float(val: f64) -> f64 {
let rounded = val.round();
if (val - rounded).abs() < 1e-10 {
rounded
} else {
val
}
}
fn format_complex(real: f64, imag: f64, suffix: char) -> String {
let real = clean_float(real);
let imag = clean_float(imag);
let real_is_zero = real.abs() < 1e-15;
let imag_is_zero = imag.abs() < 1e-15;
if real_is_zero && imag_is_zero {
return "0".to_string();
}
if imag_is_zero {
if real == real.trunc() && real.abs() < 1e15 {
return format!("{}", real as i64);
}
return format!("{}", real);
}
if real_is_zero {
if (imag - 1.0).abs() < 1e-15 {
return format!("{}", suffix);
}
if (imag + 1.0).abs() < 1e-15 {
return format!("-{}", suffix);
}
if imag == imag.trunc() && imag.abs() < 1e15 {
return format!("{}{}", imag as i64, suffix);
}
return format!("{}{}", imag, suffix);
}
let real_str = if real == real.trunc() && real.abs() < 1e15 {
format!("{}", real as i64)
} else {
format!("{}", real)
};
let imag_str = if (imag - 1.0).abs() < 1e-15 {
format!("+{}", suffix)
} else if (imag + 1.0).abs() < 1e-15 {
format!("-{}", suffix)
} else if imag > 0.0 {
if imag == imag.trunc() && imag.abs() < 1e15 {
format!("+{}{}", imag as i64, suffix)
} else {
format!("+{}{}", imag, suffix)
}
} else if imag == imag.trunc() && imag.abs() < 1e15 {
format!("{}{}", imag as i64, suffix)
} else {
format!("{}{}", imag, suffix)
};
format!("{}{}", real_str, imag_str)
}
fn coerce_complex_str(v: &LiteralValue) -> Result<String, ExcelError> {
match v {
LiteralValue::Text(s) => Ok(s.clone()),
LiteralValue::Int(i) => Ok(i.to_string()),
LiteralValue::Number(n) => Ok(n.to_string()),
LiteralValue::Error(e) => Err(e.clone()),
_ => Err(ExcelError::new_value()),
}
}
static ARG_COMPLEX_THREE: std::sync::LazyLock<Vec<ArgSchema>> =
std::sync::LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any(), ArgSchema::any()]);
#[derive(Debug)]
pub struct ComplexFn;
impl Function for ComplexFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"COMPLEX"
}
fn min_args(&self) -> usize {
2
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_COMPLEX_THREE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let real = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let imag = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let suffix = if args.len() > 2 {
match args[2].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
LiteralValue::Text(s) => {
let s = s.to_lowercase();
if s == "i" {
'i'
} else if s == "j" {
'j'
} else if s.is_empty() {
'i' } else {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
}
LiteralValue::Empty => 'i',
_ => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
}
} else {
'i'
};
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImRealFn;
impl Function for ImRealFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMREAL"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real, _, _) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(real)))
}
}
#[derive(Debug)]
pub struct ImaginaryFn;
impl Function for ImaginaryFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMAGINARY"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (_, imag, _) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(imag)))
}
}
#[derive(Debug)]
pub struct ImAbsFn;
impl Function for ImAbsFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMABS"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real, imag, _) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let abs = (real * real + imag * imag).sqrt();
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(abs)))
}
}
#[derive(Debug)]
pub struct ImArgumentFn;
impl Function for ImArgumentFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMARGUMENT"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real, imag, _) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
if real.abs() < 1e-15 && imag.abs() < 1e-15 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_div(),
)));
}
let arg = imag.atan2(real);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(arg)))
}
}
#[derive(Debug)]
pub struct ImConjugateFn;
impl Function for ImConjugateFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMCONJUGATE"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real, imag, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let result = format_complex(real, -imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
fn check_suffix_compatibility(s1: char, s2: char) -> Result<char, ExcelError> {
if s1 == s2 {
Ok(s1)
} else {
Ok(s1)
}
}
#[derive(Debug)]
pub struct ImSumFn;
impl Function for ImSumFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSUM"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let mut sum_real = 0.0;
let mut sum_imag = 0.0;
let mut result_suffix = 'i';
let mut first = true;
for arg in args {
let inumber = match arg.value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real, imag, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
if first {
result_suffix = suffix;
first = false;
} else {
result_suffix = check_suffix_compatibility(result_suffix, suffix)?;
}
sum_real += real;
sum_imag += imag;
}
let result = format_complex(sum_real, sum_imag, result_suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImSubFn;
impl Function for ImSubFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSUB"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber1 = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let inumber2 = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real1, imag1, suffix1) = match parse_complex(&inumber1) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let (real2, imag2, suffix2) = match parse_complex(&inumber2) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let result_suffix = check_suffix_compatibility(suffix1, suffix2)?;
let result = format_complex(real1 - real2, imag1 - imag2, result_suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImProductFn;
impl Function for ImProductFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMPRODUCT"
}
fn min_args(&self) -> usize {
1
}
fn variadic(&self) -> bool {
true
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let mut prod_real = 1.0;
let mut prod_imag = 0.0;
let mut result_suffix = 'i';
let mut first = true;
for arg in args {
let inumber = match arg.value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (real, imag, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
if first {
result_suffix = suffix;
prod_real = real;
prod_imag = imag;
first = false;
} else {
result_suffix = check_suffix_compatibility(result_suffix, suffix)?;
let new_real = prod_real * real - prod_imag * imag;
let new_imag = prod_real * imag + prod_imag * real;
prod_real = new_real;
prod_imag = new_imag;
}
}
let result = format_complex(prod_real, prod_imag, result_suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImDivFn;
impl Function for ImDivFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMDIV"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber1 = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let inumber2 = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix1) = match parse_complex(&inumber1) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let (c, d, suffix2) = match parse_complex(&inumber2) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let denom = c * c + d * d;
if denom.abs() < 1e-15 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_div(),
)));
}
let result_suffix = check_suffix_compatibility(suffix1, suffix2)?;
let real = (a * c + b * d) / denom;
let imag = (b * c - a * d) / denom;
let result = format_complex(real, imag, result_suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImExpFn;
impl Function for ImExpFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMEXP"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let exp_a = a.exp();
let real = exp_a * b.cos();
let imag = exp_a * b.sin();
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImLnFn;
impl Function for ImLnFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMLN"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let modulus = (a * a + b * b).sqrt();
if modulus < 1e-15 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let real = modulus.ln();
let imag = b.atan2(a);
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImLog10Fn;
impl Function for ImLog10Fn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMLOG10"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let modulus = (a * a + b * b).sqrt();
if modulus < 1e-15 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let ln10 = 10.0_f64.ln();
let real = modulus.ln() / ln10;
let imag = b.atan2(a) / ln10;
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImLog2Fn;
impl Function for ImLog2Fn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMLOG2"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let modulus = (a * a + b * b).sqrt();
if modulus < 1e-15 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
let ln2 = 2.0_f64.ln();
let real = modulus.ln() / ln2;
let imag = b.atan2(a) / ln2;
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImPowerFn;
impl Function for ImPowerFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMPOWER"
}
fn min_args(&self) -> usize {
2
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_TWO[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let n = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let modulus = (a * a + b * b).sqrt();
let theta = b.atan2(a);
if modulus < 1e-15 {
if n > 0.0 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
"0".to_string(),
)));
} else {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
}
let r_n = modulus.powf(n);
let n_theta = n * theta;
let real = r_n * n_theta.cos();
let imag = r_n * n_theta.sin();
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImSqrtFn;
impl Function for ImSqrtFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSQRT"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let modulus = (a * a + b * b).sqrt();
let theta = b.atan2(a);
let sqrt_r = modulus.sqrt();
let half_theta = theta / 2.0;
let real = sqrt_r * half_theta.cos();
let imag = sqrt_r * half_theta.sin();
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImSinFn;
impl Function for ImSinFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSIN"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let real = a.sin() * b.cosh();
let imag = a.cos() * b.sinh();
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
#[derive(Debug)]
pub struct ImCosFn;
impl Function for ImCosFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMCOS"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let real = a.cos() * b.cosh();
let imag = -a.sin() * b.sinh();
let result = format_complex(real, imag, suffix);
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
}
}
fn eval_complex_unary<'a, 'b, 'c, F>(
args: &'c [ArgumentHandle<'a, 'b>],
f: F,
) -> Result<crate::traits::CalcValue<'b>, ExcelError>
where
F: FnOnce(f64, f64, char) -> Result<(f64, f64, char), ExcelError>,
{
if args.len() != 1 {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_value(),
)));
}
let inumber = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => match coerce_complex_str(&other) {
Ok(s) => s,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
},
};
let (a, b, suffix) = match parse_complex(&inumber) {
Ok(c) => c,
Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
let (real, imag, suffix) = f(a, b, suffix)?;
if real.is_nan() || imag.is_nan() || real.is_infinite() || imag.is_infinite() {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_num(),
)));
}
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
format_complex(real, imag, suffix),
)))
}
#[derive(Debug)]
pub struct ImCoshFn;
impl Function for ImCoshFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMCOSH"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
Ok((a.cosh() * b.cos(), a.sinh() * b.sin(), suffix))
})
}
}
#[derive(Debug)]
pub struct ImSinhFn;
impl Function for ImSinhFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSINH"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
Ok((a.sinh() * b.cos(), a.cosh() * b.sin(), suffix))
})
}
}
#[derive(Debug)]
pub struct ImSecFn;
impl Function for ImSecFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSEC"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
let cos_a = a.cos();
let sin_a = a.sin();
let cosh_b = b.cosh();
let sinh_b = b.sinh();
let denom = cos_a * cos_a * cosh_b * cosh_b + sin_a * sin_a * sinh_b * sinh_b;
Ok((cos_a * cosh_b / denom, sin_a * sinh_b / denom, suffix))
})
}
}
#[derive(Debug)]
pub struct ImSechFn;
impl Function for ImSechFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMSECH"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
let cosh_a = a.cosh();
let sinh_a = a.sinh();
let cos_b = b.cos();
let sin_b = b.sin();
let denom = cosh_a * cosh_a * cos_b * cos_b + sinh_a * sinh_a * sin_b * sin_b;
Ok((cosh_a * cos_b / denom, -sinh_a * sin_b / denom, suffix))
})
}
}
#[derive(Debug)]
pub struct ImCscFn;
impl Function for ImCscFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMCSC"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
let cos_a = a.cos();
let sin_a = a.sin();
let cosh_b = b.cosh();
let sinh_b = b.sinh();
let denom = sin_a * sin_a * cosh_b * cosh_b + cos_a * cos_a * sinh_b * sinh_b;
Ok((sin_a * cosh_b / denom, -cos_a * sinh_b / denom, suffix))
})
}
}
#[derive(Debug)]
pub struct ImCschFn;
impl Function for ImCschFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMCSCH"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
let cosh_a = a.cosh();
let sinh_a = a.sinh();
let cos_b = b.cos();
let sin_b = b.sin();
let denom = sinh_a * sinh_a * cos_b * cos_b + cosh_a * cosh_a * sin_b * sin_b;
Ok((sinh_a * cos_b / denom, -cosh_a * sin_b / denom, suffix))
})
}
}
#[derive(Debug)]
pub struct ImTanFn;
impl Function for ImTanFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMTAN"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
let tan_a = a.tan();
let tanh_b = b.tanh();
let denom = 1.0 + tan_a * tan_a * tanh_b * tanh_b;
Ok((
(tan_a - tan_a * tanh_b * tanh_b) / denom,
(tanh_b + tan_a * tan_a * tanh_b) / denom,
suffix,
))
})
}
}
#[derive(Debug)]
pub struct ImCotFn;
impl Function for ImCotFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"IMCOT"
}
fn min_args(&self) -> usize {
1
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_ANY_ONE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
eval_complex_unary(args, |a, b, suffix| {
if a == 0.0 && b != 0.0 {
return Ok((0.0, -1.0 / b.tanh(), suffix));
}
if b == 0.0 {
return Ok((1.0 / a.tan(), 0.0, suffix));
}
let cot_a = 1.0 / a.tan();
let coth_b = 1.0 / b.tanh();
let denom = cot_a * cot_a + coth_b * coth_b;
Ok((
(cot_a * coth_b * coth_b - cot_a) / denom,
(-cot_a * cot_a * coth_b - coth_b) / denom,
suffix,
))
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum UnitCategory {
Length,
Mass,
Temperature,
}
struct UnitInfo {
category: UnitCategory,
to_base: f64,
}
fn get_unit_info(unit: &str) -> Option<UnitInfo> {
match unit {
"m" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 1.0,
}),
"km" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 1000.0,
}),
"cm" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 0.01,
}),
"mm" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 0.001,
}),
"mi" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 1609.344,
}),
"ft" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 0.3048,
}),
"in" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 0.0254,
}),
"yd" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 0.9144,
}),
"Nmi" => Some(UnitInfo {
category: UnitCategory::Length,
to_base: 1852.0,
}),
"g" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 1.0,
}),
"kg" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 1000.0,
}),
"mg" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 0.001,
}),
"lbm" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 453.59237,
}),
"oz" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 28.349523125,
}),
"ozm" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 28.349523125,
}),
"ton" => Some(UnitInfo {
category: UnitCategory::Mass,
to_base: 907184.74,
}),
"C" | "cel" => Some(UnitInfo {
category: UnitCategory::Temperature,
to_base: 0.0, }),
"F" | "fah" => Some(UnitInfo {
category: UnitCategory::Temperature,
to_base: 0.0, }),
"K" | "kel" => Some(UnitInfo {
category: UnitCategory::Temperature,
to_base: 0.0, }),
_ => None,
}
}
fn normalize_temp_unit(unit: &str) -> &str {
match unit {
"C" | "cel" => "C",
"F" | "fah" => "F",
"K" | "kel" => "K",
_ => unit,
}
}
fn convert_temperature(value: f64, from: &str, to: &str) -> f64 {
let from = normalize_temp_unit(from);
let to = normalize_temp_unit(to);
if from == to {
return value;
}
let celsius = match from {
"C" => value,
"F" => (value - 32.0) * 5.0 / 9.0,
"K" => value - 273.15,
_ => value,
};
match to {
"C" => celsius,
"F" => celsius * 9.0 / 5.0 + 32.0,
"K" => celsius + 273.15,
_ => celsius,
}
}
fn convert_units(value: f64, from: &str, to: &str) -> Result<f64, ExcelError> {
let from_info = get_unit_info(from).ok_or_else(ExcelError::new_na)?;
let to_info = get_unit_info(to).ok_or_else(ExcelError::new_na)?;
if from_info.category != to_info.category {
return Err(ExcelError::new_na());
}
if from_info.category == UnitCategory::Temperature {
return Ok(convert_temperature(value, from, to));
}
let base_value = value * from_info.to_base;
Ok(base_value / to_info.to_base)
}
#[derive(Debug)]
pub struct ConvertFn;
impl Function for ConvertFn {
func_caps!(PURE);
fn name(&self) -> &'static str {
"CONVERT"
}
fn min_args(&self) -> usize {
3
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&ARG_COMPLEX_THREE[..]
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
let value = match args[0].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
other => coerce_num(&other)?,
};
let from_unit = match args[1].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
LiteralValue::Text(s) => s,
_ => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_na(),
)));
}
};
let to_unit = match args[2].value()?.into_literal() {
LiteralValue::Error(e) => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
}
LiteralValue::Text(s) => s,
_ => {
return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new_na(),
)));
}
};
match convert_units(value, &from_unit, &to_unit) {
Ok(result) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
result,
))),
Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
}
}
}
pub fn register_builtins() {
use std::sync::Arc;
crate::function_registry::register_function(Arc::new(BitAndFn));
crate::function_registry::register_function(Arc::new(BitOrFn));
crate::function_registry::register_function(Arc::new(BitXorFn));
crate::function_registry::register_function(Arc::new(BitLShiftFn));
crate::function_registry::register_function(Arc::new(BitRShiftFn));
crate::function_registry::register_function(Arc::new(Bin2DecFn));
crate::function_registry::register_function(Arc::new(Dec2BinFn));
crate::function_registry::register_function(Arc::new(Hex2DecFn));
crate::function_registry::register_function(Arc::new(Dec2HexFn));
crate::function_registry::register_function(Arc::new(Oct2DecFn));
crate::function_registry::register_function(Arc::new(Dec2OctFn));
crate::function_registry::register_function(Arc::new(Bin2HexFn));
crate::function_registry::register_function(Arc::new(Hex2BinFn));
crate::function_registry::register_function(Arc::new(Bin2OctFn));
crate::function_registry::register_function(Arc::new(Oct2BinFn));
crate::function_registry::register_function(Arc::new(Hex2OctFn));
crate::function_registry::register_function(Arc::new(Oct2HexFn));
crate::function_registry::register_function(Arc::new(DeltaFn));
crate::function_registry::register_function(Arc::new(GestepFn));
crate::function_registry::register_function(Arc::new(ErfFn));
crate::function_registry::register_function(Arc::new(ErfcFn));
crate::function_registry::register_function(Arc::new(ErfPreciseFn));
crate::function_registry::register_function(Arc::new(ErfcPreciseFn));
crate::function_registry::register_function(Arc::new(BesselIFn));
crate::function_registry::register_function(Arc::new(BesselJFn));
crate::function_registry::register_function(Arc::new(BesselKFn));
crate::function_registry::register_function(Arc::new(BesselYFn));
crate::function_registry::register_function(Arc::new(ComplexFn));
crate::function_registry::register_function(Arc::new(ImRealFn));
crate::function_registry::register_function(Arc::new(ImaginaryFn));
crate::function_registry::register_function(Arc::new(ImAbsFn));
crate::function_registry::register_function(Arc::new(ImArgumentFn));
crate::function_registry::register_function(Arc::new(ImConjugateFn));
crate::function_registry::register_function(Arc::new(ImSumFn));
crate::function_registry::register_function(Arc::new(ImSubFn));
crate::function_registry::register_function(Arc::new(ImProductFn));
crate::function_registry::register_function(Arc::new(ImDivFn));
crate::function_registry::register_function(Arc::new(ImExpFn));
crate::function_registry::register_function(Arc::new(ImLnFn));
crate::function_registry::register_function(Arc::new(ImLog10Fn));
crate::function_registry::register_function(Arc::new(ImLog2Fn));
crate::function_registry::register_function(Arc::new(ImPowerFn));
crate::function_registry::register_function(Arc::new(ImSqrtFn));
crate::function_registry::register_function(Arc::new(ImSinFn));
crate::function_registry::register_function(Arc::new(ImCosFn));
crate::function_registry::register_function(Arc::new(ImCoshFn));
crate::function_registry::register_function(Arc::new(ImCotFn));
crate::function_registry::register_function(Arc::new(ImCscFn));
crate::function_registry::register_function(Arc::new(ImCschFn));
crate::function_registry::register_function(Arc::new(ImSecFn));
crate::function_registry::register_function(Arc::new(ImSechFn));
crate::function_registry::register_function(Arc::new(ImSinhFn));
crate::function_registry::register_function(Arc::new(ImTanFn));
crate::function_registry::register_function(Arc::new(ConvertFn));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builtins::text::ValueFn;
use crate::test_workbook::TestWorkbook;
use formualizer_common::{ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::parse;
use std::sync::Arc;
fn eval(formula: &str) -> LiteralValue {
let wb = TestWorkbook::new()
.with_function(Arc::new(ErfcFn))
.with_function(Arc::new(ErfcPreciseFn))
.with_function(Arc::new(BesselIFn))
.with_function(Arc::new(BesselJFn))
.with_function(Arc::new(BesselKFn))
.with_function(Arc::new(BesselYFn))
.with_function(Arc::new(ImCoshFn))
.with_function(Arc::new(ImCotFn))
.with_function(Arc::new(ImCscFn))
.with_function(Arc::new(ImCschFn))
.with_function(Arc::new(ImSecFn))
.with_function(Arc::new(ImSechFn))
.with_function(Arc::new(ImSinhFn))
.with_function(Arc::new(ImTanFn))
.with_function(Arc::new(ValueFn));
let interp = wb.interpreter();
let ast = parse(formula).expect("parse");
interp.evaluate_ast(&ast).expect("eval").into_literal()
}
fn assert_number_close(value: LiteralValue, expected: f64) {
match value {
LiteralValue::Number(n) => assert!((n - expected).abs() < 1e-12, "{n} != {expected}"),
other => panic!("expected number, got {other:?}"),
}
}
fn assert_number_rel_close(value: LiteralValue, expected: f64, tol: f64) {
match value {
LiteralValue::Number(n) => {
let denom = (n * n + expected * expected).sqrt().max(1.0);
assert!((n - expected).abs() / denom < tol, "{n} != {expected}");
}
other => panic!("expected number, got {other:?}"),
}
}
#[test]
fn erfc_precise_matches_erfc() {
assert_number_close(eval("=ERFC.PRECISE(1)"), erfc_direct(1.0));
assert_eq!(eval("=ERFC.PRECISE(0)"), LiteralValue::Number(1.0));
assert_number_close(eval("=ERFC.PRECISE(\"1\")"), erfc_direct(1.0));
}
#[test]
fn bessel_functions_match_known_values_and_excel_order_truncation() {
assert_number_rel_close(eval("=BESSELI(0.5,1)"), 0.2578943053908963, 1e-6);
assert_number_rel_close(eval("=BESSELI(0.5,1.9)"), 0.2578943053908963, 1e-6);
assert_number_rel_close(eval("=BESSELJ(0.5,3)"), 0.002563729994587244, 1e-13);
assert_number_rel_close(eval("=BESSELK(0.5,1)"), 1.656441120003301, 1e-6);
assert_number_rel_close(eval("=BESSELY(0.5,3)"), -42.059494304723883, 1e-13);
}
#[test]
fn bessel_functions_map_invalid_domains_to_num() {
assert!(
matches!(eval("=BESSELJ(1,-1)"), LiteralValue::Error(e) if e.kind == ExcelErrorKind::Num)
);
assert!(
matches!(eval("=BESSELY(1,-1)"), LiteralValue::Error(e) if e.kind == ExcelErrorKind::Num)
);
assert!(
matches!(eval("=BESSELK(0,1)"), LiteralValue::Error(e) if e.kind == ExcelErrorKind::Num)
);
assert!(
matches!(eval("=BESSELY(-1,1)"), LiteralValue::Error(e) if e.kind == ExcelErrorKind::Num)
);
}
#[test]
fn complex_hyperbolic_functions() {
assert_eq!(eval("=IMCOSH(\"0\")"), LiteralValue::Text("1".into()));
assert_eq!(eval("=IMSINH(\"0\")"), LiteralValue::Text("0".into()));
assert_eq!(eval("=IMSECH(\"0\")"), LiteralValue::Text("1".into()));
assert_eq!(eval("=IMTAN(\"0\")"), LiteralValue::Text("0".into()));
}
#[test]
fn complex_reciprocal_trig_functions() {
assert_eq!(eval("=IMSEC(\"0\")"), LiteralValue::Text("1".into()));
assert_eq!(
eval("=IMCOT(\"1\")"),
LiteralValue::Text(format_complex(1.0 / 1.0f64.tan(), 0.0, 'i'))
);
assert_eq!(
eval("=IMCSC(\"1.5707963267948966\")"),
LiteralValue::Text("1".into())
);
}
#[test]
fn complex_functions_preserve_j_suffix_and_error_on_poles() {
match eval("=IMSINH(\"j\")") {
LiteralValue::Text(s) => assert!(s.ends_with('j'), "expected j suffix, got {s}"),
other => panic!("expected text, got {other:?}"),
}
let pole = eval("=IMCSC(\"0\")");
assert!(matches!(pole, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Num));
}
}