mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-31 15:48:41 +00:00
638 lines
29 KiB
Rust
638 lines
29 KiB
Rust
//! Engineering functions: base conversion, bit operations, unit conversion, error function,
|
||
//! complex numbers and Bessel functions.
|
||
|
||
use std::f64::consts::PI;
|
||
|
||
use gridcraft_core::{CellError, Value, number_to_text};
|
||
|
||
use crate::special::{erf, erfc};
|
||
use crate::util::{A, R, S, flatten, has, num, num_val, opt_num, scalar, text, text_val};
|
||
use crate::{Arg, Ctx, FnSpec, VAR};
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Base conversion
|
||
|
||
/// Source text of a base-conversion argument (numbers are written out as integers).
|
||
fn base_source(a: &[Arg]) -> R<String> {
|
||
let v = a.first().map(scalar).unwrap_or(Value::Empty);
|
||
match v {
|
||
Value::Error(e) => Err(e),
|
||
Value::Bool(_) => Err(CellError::Value),
|
||
Value::Number(n) => {
|
||
if n < 0.0 || n != n.trunc() {
|
||
return Err(CellError::Num);
|
||
}
|
||
Ok(number_to_text(n))
|
||
}
|
||
Value::Empty => Ok(String::new()),
|
||
v => Ok(v.to_text()?.trim().to_string()),
|
||
}
|
||
}
|
||
|
||
/// Parses a 10-digit two's-complement number in `radix`.
|
||
fn parse_base(s: &str, radix: u32) -> R<i64> {
|
||
if s.len() > 10 {
|
||
return Err(CellError::Num);
|
||
}
|
||
if s.is_empty() {
|
||
return Ok(0);
|
||
}
|
||
let v = i64::from_str_radix(s, radix).map_err(|_| CellError::Num)?;
|
||
if s.starts_with(['+', '-']) {
|
||
return Err(CellError::Num);
|
||
}
|
||
let bits = match radix {
|
||
2 => 10,
|
||
8 => 30,
|
||
_ => 40,
|
||
};
|
||
// A full 10-digit number with the top bit set is negative.
|
||
if s.len() == 10 && v >= 1i64 << (bits - 1) { Ok(v - (1i64 << bits)) } else { Ok(v) }
|
||
}
|
||
|
||
/// Writes `v` in `radix`, using 10-digit two's complement for negatives and padding to `places`.
|
||
fn format_base(v: i64, radix: u32, places: Option<f64>) -> R<Value> {
|
||
let bits: u32 = match radix {
|
||
2 => 10,
|
||
8 => 30,
|
||
_ => 40,
|
||
};
|
||
let min = -(1i64 << (bits - 1));
|
||
let max = (1i64 << (bits - 1)) - 1;
|
||
if v < min || v > max {
|
||
return Err(CellError::Num);
|
||
}
|
||
if v < 0 {
|
||
let u = v + (1i64 << bits);
|
||
return text_val(to_radix(u, radix));
|
||
}
|
||
let s = to_radix(v, radix);
|
||
match places {
|
||
None => text_val(s),
|
||
Some(p) => {
|
||
let p = p.trunc();
|
||
if !(1.0..=10.0).contains(&p) {
|
||
return Err(CellError::Num);
|
||
}
|
||
let p = p as usize;
|
||
if s.len() > p {
|
||
return Err(CellError::Num);
|
||
}
|
||
text_val(format!("{}{}", "0".repeat(p - s.len()), s))
|
||
}
|
||
}
|
||
}
|
||
|
||
fn to_radix(mut v: i64, radix: u32) -> String {
|
||
if v == 0 {
|
||
return "0".into();
|
||
}
|
||
let mut out = Vec::new();
|
||
while v > 0 {
|
||
let d = (v % radix as i64) as u32;
|
||
out.push(char::from_digit(d, radix).unwrap_or('0').to_ascii_uppercase());
|
||
v /= radix as i64;
|
||
}
|
||
out.iter().rev().collect()
|
||
}
|
||
|
||
fn places_arg(a: &[Arg]) -> R<Option<f64>> {
|
||
if has(a, 1) { Ok(Some(num(a, 1)?)) } else { Ok(None) }
|
||
}
|
||
|
||
fn convert_base(a: &[Arg], from: u32, to: u32) -> R<Value> {
|
||
let s = base_source(a)?;
|
||
let v = parse_base(&s, from)?;
|
||
let places = places_arg(a)?;
|
||
if to == 10 {
|
||
return Ok(Value::Number(v as f64));
|
||
}
|
||
format_base(v, to, places)
|
||
}
|
||
|
||
fn dec_to(a: &[Arg], to: u32) -> R<Value> {
|
||
let v = a.first().map(scalar).unwrap_or(Value::Empty);
|
||
if matches!(v, Value::Bool(_)) {
|
||
return Err(CellError::Value);
|
||
}
|
||
let n = v.to_number()?.trunc();
|
||
if n.abs() > 1e15 {
|
||
return Err(CellError::Num);
|
||
}
|
||
format_base(n as i64, to, places_arg(a)?)
|
||
}
|
||
|
||
macro_rules! base_fn {
|
||
($name:ident, $from:expr, $to:expr) => {
|
||
fn $name(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
convert_base(a, $from, $to)
|
||
}
|
||
};
|
||
}
|
||
base_fn!(bin2dec, 2, 10);
|
||
base_fn!(bin2hex, 2, 16);
|
||
base_fn!(bin2oct, 2, 8);
|
||
base_fn!(hex2bin, 16, 2);
|
||
base_fn!(hex2dec, 16, 10);
|
||
base_fn!(hex2oct, 16, 8);
|
||
base_fn!(oct2bin, 8, 2);
|
||
base_fn!(oct2dec, 8, 10);
|
||
base_fn!(oct2hex, 8, 16);
|
||
|
||
fn dec2bin(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
dec_to(a, 2)
|
||
}
|
||
fn dec2oct(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
dec_to(a, 8)
|
||
}
|
||
fn dec2hex(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
dec_to(a, 16)
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Bits
|
||
|
||
const BIT_LIMIT: f64 = 281_474_976_710_656.0; // 2^48
|
||
|
||
fn bit_operand(a: &[Arg], i: usize) -> R<u64> {
|
||
let n = num(a, i)?;
|
||
if !(0.0..BIT_LIMIT).contains(&n) || n != n.trunc() {
|
||
return Err(CellError::Num);
|
||
}
|
||
Ok(n as u64)
|
||
}
|
||
|
||
fn bitand(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
Ok(Value::Number((bit_operand(a, 0)? & bit_operand(a, 1)?) as f64))
|
||
}
|
||
fn bitor(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
Ok(Value::Number((bit_operand(a, 0)? | bit_operand(a, 1)?) as f64))
|
||
}
|
||
fn bitxor(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
Ok(Value::Number((bit_operand(a, 0)? ^ bit_operand(a, 1)?) as f64))
|
||
}
|
||
|
||
fn shift(a: &[Arg], left: bool) -> R<Value> {
|
||
let n = bit_operand(a, 0)?;
|
||
let s = num(a, 1)?.trunc();
|
||
if n == 0 {
|
||
return Ok(Value::Number(0.0));
|
||
}
|
||
if s.abs() > 53.0 {
|
||
return Err(CellError::Num);
|
||
}
|
||
let s = if left { s as i64 } else { -(s as i64) };
|
||
let r = if s >= 0 {
|
||
let r = (n as u128) << s;
|
||
if r >= BIT_LIMIT as u128 {
|
||
return Err(CellError::Num);
|
||
}
|
||
r as u64
|
||
} else {
|
||
n >> (-s)
|
||
};
|
||
Ok(Value::Number(r as f64))
|
||
}
|
||
fn bitlshift(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
shift(a, true)
|
||
}
|
||
fn bitrshift(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
shift(a, false)
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// DELTA, GESTEP, ERF
|
||
|
||
fn delta(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
let x = num(a, 0)?;
|
||
let y = opt_num(a, 1, 0.0)?;
|
||
Ok(Value::Number(if x == y { 1.0 } else { 0.0 }))
|
||
}
|
||
fn gestep(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
let x = num(a, 0)?;
|
||
let y = opt_num(a, 1, 0.0)?;
|
||
Ok(Value::Number(if x >= y { 1.0 } else { 0.0 }))
|
||
}
|
||
fn erf_fn(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
let lo = num(a, 0)?;
|
||
if has(a, 1) {
|
||
let hi = num(a, 1)?;
|
||
return num_val(erf(hi) - erf(lo));
|
||
}
|
||
num_val(erf(lo))
|
||
}
|
||
fn erf_precise(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
num_val(erf(num(a, 0)?))
|
||
}
|
||
fn erfc_fn(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
num_val(erfc(num(a, 0)?))
|
||
}
|
||
|
||
macro_rules! im1 {
|
||
($name:ident, $f:expr) => {
|
||
fn $name(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||
im_unary(a, $f)
|
||
}
|
||
};
|
||
}
|
||
|
||
mod bessel;
|
||
mod complex;
|
||
mod units;
|
||
|
||
use bessel::{besseli, besselj, besselk, bessely};
|
||
#[cfg(test)]
|
||
use complex::parse_complex;
|
||
use complex::{Cx, ONE, complex, im_unary, imabs, imaginary, imargument, imdiv, impower, improduct, imreal, imsub, imsum};
|
||
use units::convert;
|
||
|
||
im1!(imconjugate, |z: Cx| Ok(Cx::new(z.re, -z.im)));
|
||
im1!(imexp, |z: Cx| Ok(z.exp()));
|
||
im1!(imln, |z: Cx| z.ln());
|
||
im1!(imlog10, |z: Cx| z.ln().map(|l| Cx::new(l.re / std::f64::consts::LN_10, l.im / std::f64::consts::LN_10)));
|
||
im1!(imlog2, |z: Cx| z.ln().map(|l| Cx::new(l.re / std::f64::consts::LN_2, l.im / std::f64::consts::LN_2)));
|
||
// Excel takes the polar route, so IMSQRT("-4") is 1.22514845490862E-16+2i, not 2i. Its cosine
|
||
// reduces its argument with a 64-bit-mantissa pi, which is PI/2 + 2.5e-20 and shows up as the
|
||
// stray real part. `+ 0.0` turns a -0 imaginary part into +0, so the angle stays +pi.
|
||
// shortcut: parts above ~1E154 or below ~1E-155 keep the exact result (Excel gives #NUM! or 0), no known use.
|
||
const HALF_PI_EXCESS: f64 = 2.508_278_806_33e-20;
|
||
im1!(imsqrt, |z: Cx| {
|
||
let im = z.im + 0.0;
|
||
let theta = match z.re {
|
||
0.0 => {
|
||
if im == 0.0 {
|
||
0.0
|
||
} else {
|
||
std::f64::consts::FRAC_PI_2.copysign(im)
|
||
}
|
||
}
|
||
x if x > 0.0 => (im / x).atan(),
|
||
x => (im / x).atan() + if im < 0.0 { -std::f64::consts::PI } else { std::f64::consts::PI },
|
||
};
|
||
let (half, root) = (theta / 2.0, z.abs().sqrt());
|
||
Ok(Cx::new(root * (half.cos() + HALF_PI_EXCESS * half.abs().sin()), root * half.sin()))
|
||
});
|
||
im1!(imsin, |z: Cx| Ok(z.sin()));
|
||
im1!(imcos, |z: Cx| Ok(z.cos()));
|
||
im1!(imtan, |z: Cx| z.sin().div(z.cos()));
|
||
im1!(imcot, |z: Cx| z.cos().div(z.sin()));
|
||
im1!(imsec, |z: Cx| ONE.div(z.cos()));
|
||
im1!(imcsc, |z: Cx| ONE.div(z.sin()));
|
||
im1!(imsinh, |z: Cx| Ok(z.sinh()));
|
||
im1!(imcosh, |z: Cx| Ok(z.cosh()));
|
||
im1!(imsech, |z: Cx| ONE.div(z.cosh()));
|
||
im1!(imcsch, |z: Cx| ONE.div(z.sinh()));
|
||
|
||
pub(crate) fn specs() -> Vec<FnSpec> {
|
||
vec![
|
||
f!("BIN2DEC", 1, 1, Engineering, S, "BIN2DEC(number)", "Converts a binary number to decimal.", bin2dec),
|
||
f!("BIN2HEX", 1, 2, Engineering, S, "BIN2HEX(number, [places])", "Converts a binary number to hexadecimal.", bin2hex),
|
||
f!("BIN2OCT", 1, 2, Engineering, S, "BIN2OCT(number, [places])", "Converts a binary number to octal.", bin2oct),
|
||
f!("DEC2BIN", 1, 2, Engineering, S, "DEC2BIN(number, [places])", "Converts a decimal number to binary.", dec2bin),
|
||
f!("DEC2HEX", 1, 2, Engineering, S, "DEC2HEX(number, [places])", "Converts a decimal number to hexadecimal.", dec2hex),
|
||
f!("DEC2OCT", 1, 2, Engineering, S, "DEC2OCT(number, [places])", "Converts a decimal number to octal.", dec2oct),
|
||
f!("HEX2BIN", 1, 2, Engineering, S, "HEX2BIN(number, [places])", "Converts a hexadecimal number to binary.", hex2bin),
|
||
f!("HEX2DEC", 1, 1, Engineering, S, "HEX2DEC(number)", "Converts a hexadecimal number to decimal.", hex2dec),
|
||
f!("HEX2OCT", 1, 2, Engineering, S, "HEX2OCT(number, [places])", "Converts a hexadecimal number to octal.", hex2oct),
|
||
f!("OCT2BIN", 1, 2, Engineering, S, "OCT2BIN(number, [places])", "Converts an octal number to binary.", oct2bin),
|
||
f!("OCT2DEC", 1, 1, Engineering, S, "OCT2DEC(number)", "Converts an octal number to decimal.", oct2dec),
|
||
f!("OCT2HEX", 1, 2, Engineering, S, "OCT2HEX(number, [places])", "Converts an octal number to hexadecimal.", oct2hex),
|
||
f!("BITAND", 2, 2, Engineering, S, "BITAND(number1, number2)", "Bitwise AND of two non-negative integers.", bitand),
|
||
f!("BITOR", 2, 2, Engineering, S, "BITOR(number1, number2)", "Bitwise OR of two non-negative integers.", bitor),
|
||
f!("BITXOR", 2, 2, Engineering, S, "BITXOR(number1, number2)", "Bitwise exclusive OR of two non-negative integers.", bitxor),
|
||
f!(
|
||
"BITLSHIFT",
|
||
2,
|
||
2,
|
||
Engineering,
|
||
S,
|
||
"BITLSHIFT(number, shift_amount)",
|
||
"Shifts the bits of a number left (right for negative shifts).",
|
||
bitlshift
|
||
),
|
||
f!(
|
||
"BITRSHIFT",
|
||
2,
|
||
2,
|
||
Engineering,
|
||
S,
|
||
"BITRSHIFT(number, shift_amount)",
|
||
"Shifts the bits of a number right (left for negative shifts).",
|
||
bitrshift
|
||
),
|
||
f!("CONVERT", 3, 3, Engineering, S, "CONVERT(number, from_unit, to_unit)", "Converts a measurement from one unit to another.", convert),
|
||
f!("DELTA", 1, 2, Engineering, S, "DELTA(number1, [number2])", "Returns 1 when two numbers are equal, otherwise 0.", delta),
|
||
f!("GESTEP", 1, 2, Engineering, S, "GESTEP(number, [step])", "Returns 1 when a number is at least the step, otherwise 0.", gestep),
|
||
f!(
|
||
"ERF",
|
||
1,
|
||
2,
|
||
Engineering,
|
||
S,
|
||
"ERF(lower_limit, [upper_limit])",
|
||
"Error function integrated from 0 to a limit, or between two limits.",
|
||
erf_fn
|
||
),
|
||
f!("ERF.PRECISE", 1, 1, Engineering, S, "ERF.PRECISE(x)", "Error function integrated from 0 to x.", erf_precise),
|
||
f!("ERFC", 1, 1, Engineering, S, "ERFC(x)", "Complementary error function, 1 - ERF(x).", erfc_fn),
|
||
f!("ERFC.PRECISE", 1, 1, Engineering, S, "ERFC.PRECISE(x)", "Complementary error function, 1 - ERF(x).", erfc_fn),
|
||
f!(
|
||
"COMPLEX",
|
||
2,
|
||
3,
|
||
Engineering,
|
||
S,
|
||
"COMPLEX(real_num, i_num, [suffix])",
|
||
"Builds a complex number as text from real and imaginary parts.",
|
||
complex
|
||
),
|
||
f!("IMABS", 1, 1, Engineering, S, "IMABS(inumber)", "Absolute value (modulus) of a complex number.", imabs),
|
||
f!("IMAGINARY", 1, 1, Engineering, S, "IMAGINARY(inumber)", "Imaginary coefficient of a complex number.", imaginary),
|
||
f!("IMREAL", 1, 1, Engineering, S, "IMREAL(inumber)", "Real coefficient of a complex number.", imreal),
|
||
f!("IMARGUMENT", 1, 1, Engineering, S, "IMARGUMENT(inumber)", "Angle of a complex number in radians.", imargument),
|
||
f!("IMCONJUGATE", 1, 1, Engineering, S, "IMCONJUGATE(inumber)", "Complex conjugate of a complex number.", imconjugate),
|
||
f!("IMCOS", 1, 1, Engineering, S, "IMCOS(inumber)", "Cosine of a complex number.", imcos),
|
||
f!("IMCOSH", 1, 1, Engineering, S, "IMCOSH(inumber)", "Hyperbolic cosine of a complex number.", imcosh),
|
||
f!("IMCOT", 1, 1, Engineering, S, "IMCOT(inumber)", "Cotangent of a complex number.", imcot),
|
||
f!("IMCSC", 1, 1, Engineering, S, "IMCSC(inumber)", "Cosecant of a complex number.", imcsc),
|
||
f!("IMCSCH", 1, 1, Engineering, S, "IMCSCH(inumber)", "Hyperbolic cosecant of a complex number.", imcsch),
|
||
f!("IMDIV", 2, 2, Engineering, S, "IMDIV(inumber1, inumber2)", "Quotient of two complex numbers.", imdiv),
|
||
f!("IMEXP", 1, 1, Engineering, S, "IMEXP(inumber)", "Exponential of a complex number.", imexp),
|
||
f!("IMLN", 1, 1, Engineering, S, "IMLN(inumber)", "Natural logarithm of a complex number.", imln),
|
||
f!("IMLOG10", 1, 1, Engineering, S, "IMLOG10(inumber)", "Base-10 logarithm of a complex number.", imlog10),
|
||
f!("IMLOG2", 1, 1, Engineering, S, "IMLOG2(inumber)", "Base-2 logarithm of a complex number.", imlog2),
|
||
f!("IMPOWER", 2, 2, Engineering, S, "IMPOWER(inumber, number)", "Raises a complex number to a power.", impower),
|
||
f!("IMPRODUCT", 1, VAR, Engineering, A, "IMPRODUCT(inumber1, [inumber2], ...)", "Product of complex numbers.", improduct),
|
||
f!("IMSEC", 1, 1, Engineering, S, "IMSEC(inumber)", "Secant of a complex number.", imsec),
|
||
f!("IMSECH", 1, 1, Engineering, S, "IMSECH(inumber)", "Hyperbolic secant of a complex number.", imsech),
|
||
f!("IMSIN", 1, 1, Engineering, S, "IMSIN(inumber)", "Sine of a complex number.", imsin),
|
||
f!("IMSINH", 1, 1, Engineering, S, "IMSINH(inumber)", "Hyperbolic sine of a complex number.", imsinh),
|
||
f!("IMSQRT", 1, 1, Engineering, S, "IMSQRT(inumber)", "Square root of a complex number.", imsqrt),
|
||
f!("IMSUB", 2, 2, Engineering, S, "IMSUB(inumber1, inumber2)", "Difference of two complex numbers.", imsub),
|
||
f!("IMSUM", 1, VAR, Engineering, A, "IMSUM(inumber1, [inumber2], ...)", "Sum of complex numbers.", imsum),
|
||
f!("IMTAN", 1, 1, Engineering, S, "IMTAN(inumber)", "Tangent of a complex number.", imtan),
|
||
f!("BESSELJ", 2, 2, Engineering, S, "BESSELJ(x, n)", "Bessel function of the first kind, J_n(x).", besselj),
|
||
f!("BESSELY", 2, 2, Engineering, S, "BESSELY(x, n)", "Bessel function of the second kind, Y_n(x).", bessely),
|
||
f!("BESSELI", 2, 2, Engineering, S, "BESSELI(x, n)", "Modified Bessel function of the first kind, I_n(x).", besseli),
|
||
f!("BESSELK", 2, 2, Engineering, S, "BESSELK(x, n)", "Modified Bessel function of the second kind, K_n(x).", besselk),
|
||
]
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use gridcraft_core::{CellError, Value};
|
||
|
||
use super::parse_complex;
|
||
use crate::util::testutil::*;
|
||
|
||
#[track_caller]
|
||
fn cx(v: Value, re: f64, im: f64) {
|
||
let Value::Text(s) = &v else { panic!("expected complex text, got {v:?}") };
|
||
let (z, _) = parse_complex(s).unwrap();
|
||
let tol = 1e-12;
|
||
assert!((z.re - re).abs() <= tol * re.abs().max(1.0), "{s}: re {} vs {re}", z.re);
|
||
assert!((z.im - im).abs() <= tol * im.abs().max(1.0), "{s}: im {} vs {im}", z.im);
|
||
}
|
||
|
||
#[test]
|
||
fn base_conversions() {
|
||
close(ev("BIN2DEC", vec![n(1100100.0)]), 100.0);
|
||
close(ev("BIN2DEC", vec![n(1111111111.0)]), -1.0);
|
||
is_text(ev("BIN2HEX", vec![n(11111011.0), n(4.0)]), "00FB");
|
||
is_text(ev("BIN2HEX", vec![n(1110.0)]), "E");
|
||
is_text(ev("BIN2HEX", vec![n(1111111111.0)]), "FFFFFFFFFF");
|
||
is_text(ev("BIN2OCT", vec![n(1001.0), n(3.0)]), "011");
|
||
is_text(ev("BIN2OCT", vec![n(1111111111.0)]), "7777777777");
|
||
is_text(ev("DEC2BIN", vec![n(9.0), n(4.0)]), "1001");
|
||
is_text(ev("DEC2BIN", vec![n(-100.0)]), "1110011100");
|
||
is_err(ev("DEC2BIN", vec![n(512.0)]), CellError::Num);
|
||
is_text(ev("DEC2HEX", vec![n(100.0), n(4.0)]), "0064");
|
||
is_text(ev("DEC2HEX", vec![n(-54.0)]), "FFFFFFFFCA");
|
||
is_text(ev("DEC2HEX", vec![n(28.0)]), "1C");
|
||
is_err(ev("DEC2HEX", vec![n(64.0), n(1.0)]), CellError::Num);
|
||
is_text(ev("DEC2OCT", vec![n(58.0), n(3.0)]), "072");
|
||
is_text(ev("DEC2OCT", vec![n(-100.0)]), "7777777634");
|
||
is_text(ev("HEX2BIN", vec![t("F"), n(8.0)]), "00001111");
|
||
is_text(ev("HEX2BIN", vec![t("B7")]), "10110111");
|
||
is_text(ev("HEX2BIN", vec![t("FFFFFFFE00")]), "1000000000");
|
||
is_err(ev("HEX2BIN", vec![t("200")]), CellError::Num);
|
||
close(ev("HEX2DEC", vec![t("A5")]), 165.0);
|
||
close(ev("HEX2DEC", vec![t("FFFFFFFF5B")]), -165.0);
|
||
close(ev("HEX2DEC", vec![t("3DA408B9")]), 1034160313.0);
|
||
is_text(ev("HEX2OCT", vec![t("F"), n(3.0)]), "017");
|
||
is_text(ev("HEX2OCT", vec![t("3B4E")]), "35516");
|
||
is_text(ev("HEX2OCT", vec![t("FFFFFFFF00")]), "7777777400");
|
||
is_text(ev("OCT2BIN", vec![n(3.0), n(3.0)]), "011");
|
||
is_text(ev("OCT2BIN", vec![n(7777777000.0)]), "1000000000");
|
||
close(ev("OCT2DEC", vec![n(54.0)]), 44.0);
|
||
close(ev("OCT2DEC", vec![n(7777777533.0)]), -165.0);
|
||
is_text(ev("OCT2HEX", vec![n(100.0), n(4.0)]), "0040");
|
||
is_text(ev("OCT2HEX", vec![n(7777777533.0)]), "FFFFFFFF5B");
|
||
is_err(ev("BIN2DEC", vec![n(102.0)]), CellError::Num);
|
||
is_err(ev("HEX2DEC", vec![t("GG")]), CellError::Num);
|
||
is_err(ev("BIN2DEC", vec![t("11111111111")]), CellError::Num);
|
||
is_err(ev("DEC2BIN", vec![t("abc")]), CellError::Value);
|
||
is_text(ev("DEC2BIN", vec![n(9.7)]), "1001");
|
||
close(ev("HEX2DEC", vec![t("ff")]), 255.0);
|
||
}
|
||
|
||
#[test]
|
||
fn bits() {
|
||
close(ev("BITAND", vec![n(1.0), n(5.0)]), 1.0);
|
||
close(ev("BITAND", vec![n(13.0), n(25.0)]), 9.0);
|
||
close(ev("BITOR", vec![n(23.0), n(10.0)]), 31.0);
|
||
close(ev("BITXOR", vec![n(5.0), n(3.0)]), 6.0);
|
||
close(ev("BITLSHIFT", vec![n(4.0), n(2.0)]), 16.0);
|
||
close(ev("BITRSHIFT", vec![n(13.0), n(2.0)]), 3.0);
|
||
close(ev("BITRSHIFT", vec![n(13.0), n(-2.0)]), 52.0);
|
||
is_err(ev("BITAND", vec![n(-1.0), n(5.0)]), CellError::Num);
|
||
is_err(ev("BITAND", vec![n(1.5), n(5.0)]), CellError::Num);
|
||
is_err(ev("BITLSHIFT", vec![n(1.0), n(54.0)]), CellError::Num);
|
||
is_err(ev("BITLSHIFT", vec![n(2f64.powi(47)), n(1.0)]), CellError::Num);
|
||
for f in ["BITLSHIFT", "BITRSHIFT"] {
|
||
for shift in [-100.0, -54.0, 54.0, 100.0] {
|
||
close(ev(f, vec![n(0.0), n(shift)]), 0.0);
|
||
}
|
||
is_err(ev(f, vec![n(1.0), n(54.0)]), CellError::Num);
|
||
}
|
||
is_err(ev("BITLSHIFT", vec![n(0.0), t("bad")]), CellError::Value);
|
||
is_err(ev("BITRSHIFT", vec![n(-1.0), n(100.0)]), CellError::Num);
|
||
}
|
||
|
||
#[test]
|
||
fn convert_units() {
|
||
close(ev("CONVERT", vec![n(1.0), t("lbm"), t("kg")]), 0.45359237);
|
||
close(ev("CONVERT", vec![n(68.0), t("F"), t("C")]), 20.0);
|
||
close(ev("CONVERT", vec![n(100.0), t("C"), t("F")]), 212.0);
|
||
close(ev("CONVERT", vec![n(0.0), t("C"), t("K")]), 273.15);
|
||
close(ev("CONVERT", vec![n(1.0), t("in"), t("cm")]), 2.54);
|
||
close(ev("CONVERT", vec![n(1.0), t("mi"), t("km")]), 1.609344);
|
||
close(ev("CONVERT", vec![n(1.0), t("gal"), t("l")]), 3.785411784);
|
||
close(ev("CONVERT", vec![n(1.0), t("byte"), t("bit")]), 8.0);
|
||
close(ev("CONVERT", vec![n(1.0), t("kibyte"), t("byte")]), 1024.0);
|
||
close(ev("CONVERT", vec![n(100.0), t("km/h"), t("m/s")]), 100.0 / 3.6);
|
||
close(ev("CONVERT", vec![n(1.0), t("m2"), t("ft2")]), 10.763910416709722);
|
||
close(ev("CONVERT", vec![n(1.0), t("km2"), t("m2")]), 1e6);
|
||
close(ev("CONVERT", vec![n(1.0), t("hr"), t("mn")]), 60.0);
|
||
close(ev("CONVERT", vec![n(1.0), t("atm"), t("Pa")]), 101325.0);
|
||
close(ev("CONVERT", vec![n(1.0), t("ha"), t("m^2")]), 10000.0);
|
||
close(ev("CONVERT", vec![n(1.0), t("cal"), t("J")]), 4.1868);
|
||
close(ev("CONVERT", vec![n(1.0), t("mm"), t("m")]), 0.001);
|
||
close(ev("CONVERT", vec![n(6.0), t("ft"), t("yd")]), 2.0);
|
||
close(ev("CONVERT", vec![n(1.0), t("cup"), t("tsp")]), 48.0);
|
||
is_err(ev("CONVERT", vec![n(2.5), t("ft"), t("sec")]), CellError::NA);
|
||
is_err(ev("CONVERT", vec![n(1.0), t("xyz"), t("m")]), CellError::NA);
|
||
is_err(ev("CONVERT", vec![n(1.0), t("kft"), t("m")]), CellError::NA);
|
||
is_err(ev("CONVERT", vec![n(1.0), t("LBM"), t("kg")]), CellError::NA);
|
||
}
|
||
|
||
#[test]
|
||
fn delta_gestep_erf() {
|
||
close(ev("DELTA", vec![n(5.0), n(4.0)]), 0.0);
|
||
close(ev("DELTA", vec![n(5.0), n(5.0)]), 1.0);
|
||
close(ev("DELTA", vec![n(0.5)]), 0.0);
|
||
close(ev("GESTEP", vec![n(5.0), n(4.0)]), 1.0);
|
||
close(ev("GESTEP", vec![n(-4.0)]), 0.0);
|
||
close_tol(ev("ERF", vec![n(0.745)]), 0.707_928_920, 1e-9);
|
||
close_tol(ev("ERF", vec![n(1.0)]), 0.842_700_792_949_715, 1e-12);
|
||
close_tol(ev("ERF", vec![n(0.0), n(1.5)]), 0.966_105_146_475_311, 1e-12);
|
||
close_tol(ev("ERF.PRECISE", vec![n(1.0)]), 0.842_700_792_949_715, 1e-12);
|
||
close_tol(ev("ERFC", vec![n(1.0)]), 0.157_299_207_050_285, 1e-12);
|
||
close_tol(ev("ERFC.PRECISE", vec![n(-1.0)]), 1.842_700_792_949_715, 1e-12);
|
||
}
|
||
|
||
#[test]
|
||
fn complex_numbers() {
|
||
is_text(ev("COMPLEX", vec![n(3.0), n(4.0)]), "3+4i");
|
||
is_text(ev("COMPLEX", vec![n(3.0), n(4.0), t("j")]), "3+4j");
|
||
is_text(ev("COMPLEX", vec![n(0.0), n(1.0)]), "i");
|
||
is_text(ev("COMPLEX", vec![n(0.0), n(-1.0)]), "-i");
|
||
is_text(ev("COMPLEX", vec![n(1.5), n(-2.0)]), "1.5-2i");
|
||
is_text(ev("COMPLEX", vec![n(3.0), n(0.0)]), "3");
|
||
is_text(ev("COMPLEX", vec![n(0.0), n(0.0)]), "0");
|
||
is_err(ev("COMPLEX", vec![n(1.0), n(1.0), t("k")]), CellError::Value);
|
||
close(ev("IMABS", vec![t("5+12i")]), 13.0);
|
||
close(ev("IMREAL", vec![t("6-9i")]), 6.0);
|
||
close(ev("IMAGINARY", vec![t("3+4i")]), 4.0);
|
||
close(ev("IMAGINARY", vec![t("-j")]), -1.0);
|
||
close(ev("IMAGINARY", vec![t("4")]), 0.0);
|
||
close(ev("IMREAL", vec![n(7.0)]), 7.0);
|
||
close(ev("IMARGUMENT", vec![t("3+4i")]), 0.927_295_218_001_612_2);
|
||
is_err(ev("IMARGUMENT", vec![t("0")]), CellError::Div0);
|
||
is_text(ev("IMCONJUGATE", vec![t("3+4i")]), "3-4i");
|
||
is_text(ev("IMDIV", vec![t("-238+240i"), t("10+24i")]), "5+12i");
|
||
is_err(ev("IMDIV", vec![t("1+i"), t("0")]), CellError::Num);
|
||
is_text(ev("IMPRODUCT", vec![t("3+4i"), t("5-3i")]), "27+11i");
|
||
is_text(ev("IMPRODUCT", vec![t("1+2i"), n(30.0)]), "30+60i");
|
||
is_text(ev("IMSUM", vec![t("3+4i"), t("5-3i")]), "8+i");
|
||
is_text(ev("IMSUM", vec![rf(arr(vec![vec![tv("1+i"), Value::Empty, tv("2")]]))]), "3+i");
|
||
is_text(ev("IMSUB", vec![t("13+4i"), t("5+3i")]), "8+i");
|
||
is_err(ev("IMSUM", vec![t("1+i"), t("1+j")]), CellError::Value);
|
||
is_err(ev("IMABS", vec![t("abc")]), CellError::Num);
|
||
is_err(ev("IMABS", vec![b(true)]), CellError::Value);
|
||
cx(ev("IMEXP", vec![t("1+i")]), 1.468_693_939_915_885_2, 2.287_355_287_178_842);
|
||
cx(ev("IMLN", vec![t("3+4i")]), 1.609_437_912_434_100_3, 0.927_295_218_001_612_2);
|
||
cx(ev("IMLOG10", vec![t("3+4i")]), 0.698_970_004_336_018_8, 0.402_719_196_273_373_2);
|
||
cx(ev("IMLOG2", vec![t("3+4i")]), 2.321_928_094_887_362, 1.337_804_212_450_976_8);
|
||
cx(ev("IMSQRT", vec![t("1+i")]), 1.098_684_113_467_809_9, 0.455_089_860_562_227_3);
|
||
// Strings read back from Excel (16.0): its stray real part on negative reals included.
|
||
for (input, want) in [
|
||
("-4", "1.22514845490862E-16+2i"),
|
||
("-9", "1.83772268236293E-16+3i"),
|
||
("-1", "6.1257422745431E-17+i"),
|
||
("-2", "8.66310780426106E-17+1.4142135623731i"),
|
||
("-100", "6.1257422745431E-16+10i"),
|
||
("-1E10", "6.1257422745431E-12+100000i"),
|
||
("-1E-10", "6.1257422745431E-22+0.00001i"),
|
||
("-4+0j", "1.22514845490862E-16+2j"),
|
||
("-4-0i", "1.22514845490862E-16+2i"),
|
||
("-4-1E-20i", "1.22514845490862E-16-2i"),
|
||
("-1+1E-10i", "5.00000653944413E-11+i"),
|
||
("-0.5-1E-12i", "7.07212958930194E-13-0.707106781186548i"),
|
||
("4", "2"),
|
||
("2", "1.4142135623731"),
|
||
("0", "0"),
|
||
("3+4i", "2+i"),
|
||
("3+4j", "2+j"),
|
||
("-3+4i", "1+2i"),
|
||
("-3-4i", "1-2i"),
|
||
("-2i", "1-i"),
|
||
("2i", "1+i"),
|
||
("i", "0.707106781186548+0.707106781186547i"),
|
||
("5-12i", "3-2i"),
|
||
("-5+12i", "2+3i"),
|
||
("1E10+1E10i", "109868.411346781+45508.9860562227i"),
|
||
] {
|
||
is_text(ev("IMSQRT", vec![t(input)]), want);
|
||
}
|
||
is_text(ev("IMSQRT", vec![t("4")]), "2");
|
||
is_text(ev("IMSQRT", vec![t("-2i")]), "1-i");
|
||
is_text(ev("IMPOWER", vec![t("2+3i"), n(3.0)]), "-46+9i");
|
||
cx(ev("IMPOWER", vec![t("2+3i"), n(0.5)]), 1.674_149_228_035_540_2, 0.895_977_476_129_838);
|
||
cx(ev("IMSIN", vec![t("3+4i")]), 3.853_738_037_919_377, -27.016_813_258_003_93);
|
||
cx(ev("IMCOS", vec![t("1+i")]), 0.833_730_025_131_149, -0.988_897_705_762_865);
|
||
cx(ev("IMSINH", vec![t("4+3i")]), -27.016_813_258_003_93, 3.853_738_037_919_377);
|
||
cx(ev("IMCOSH", vec![t("4+3i")]), -27.034_945_603_074_224, 3.851_153_334_811_777);
|
||
cx(ev("IMTAN", vec![t("4+3i")]), 0.004_908_258_067_496_06, 1.000_709_536_067_233);
|
||
cx(ev("IMCOT", vec![t("4+3i")]), 0.004_901_182_394_304_4, -0.999_266_927_805_902);
|
||
cx(ev("IMSEC", vec![t("4+3i")]), -0.065_294_027_857_947_1, -0.075_224_960_302_773_2);
|
||
cx(ev("IMCSC", vec![t("4+3i")]), -0.075_489_832_915_863_7, 0.064_877_471_370_635_5);
|
||
is_text(ev("IMSECH", vec![t("0")]), "1");
|
||
is_err(ev("IMCSCH", vec![t("0")]), CellError::Num);
|
||
// sech z · cosh z = 1
|
||
let Value::Text(c) = ev("IMCOSH", vec![t("4+3i")]) else { panic!() };
|
||
let Value::Text(sc) = ev("IMSECH", vec![t("4+3i")]) else { panic!() };
|
||
cx(ev("IMPRODUCT", vec![t(&c), t(&sc)]), 1.0, 0.0);
|
||
let Value::Text(sh) = ev("IMSINH", vec![t("4+3i")]) else { panic!() };
|
||
let Value::Text(csh) = ev("IMCSCH", vec![t("4+3i")]) else { panic!() };
|
||
cx(ev("IMPRODUCT", vec![t(&sh), t(&csh)]), 1.0, 0.0);
|
||
cx(ev("IMSUM", vec![t("1.5e1+2E-1j")]), 15.0, 0.2);
|
||
}
|
||
|
||
#[test]
|
||
fn bessel() {
|
||
close_tol(ev("BESSELJ", vec![n(1.9), n(2.0)]), 0.329_925_829, 1e-6);
|
||
close_tol(ev("BESSELY", vec![n(2.5), n(1.0)]), 0.145_918_138, 1e-6);
|
||
close_tol(ev("BESSELI", vec![n(1.5), n(1.0)]), 0.981_666_428, 1e-6);
|
||
close_tol(ev("BESSELK", vec![n(1.5), n(1.0)]), 0.277_387_804, 1e-6);
|
||
close_tol(ev("BESSELJ", vec![n(1.0), n(0.0)]), 0.765_197_686_557_966_6, 1e-12);
|
||
close_tol(ev("BESSELJ", vec![n(1.0), n(1.0)]), 0.440_050_585_744_933_5, 1e-12);
|
||
close_tol(ev("BESSELJ", vec![n(-1.0), n(1.0)]), -0.440_050_585_744_933_5, 1e-12);
|
||
close_tol(ev("BESSELY", vec![n(1.0), n(0.0)]), 0.088_256_964_215_676_96, 1e-11);
|
||
close_tol(ev("BESSELY", vec![n(1.0), n(1.0)]), -0.781_212_821_300_288_7, 1e-11);
|
||
close_tol(ev("BESSELI", vec![n(1.0), n(0.0)]), 1.266_065_877_752_008_4, 1e-12);
|
||
close_tol(ev("BESSELI", vec![n(1.0), n(1.0)]), 0.565_159_103_992_485_1, 1e-12);
|
||
close_tol(ev("BESSELK", vec![n(1.0), n(0.0)]), 0.421_024_438_240_708_3, 1e-11);
|
||
close_tol(ev("BESSELK", vec![n(1.0), n(1.0)]), 0.601_907_230_197_234_6, 1e-11);
|
||
is_err(ev("BESSELJ", vec![n(1.0), n(-1.0)]), CellError::Num);
|
||
is_err(ev("BESSELY", vec![n(0.0), n(1.0)]), CellError::Num);
|
||
is_err(ev("BESSELK", vec![n(-1.0), n(1.0)]), CellError::Num);
|
||
is_err(ev("BESSELJ", vec![t("x"), n(1.0)]), CellError::Value);
|
||
// Wronskian J_{1}Y_0 − J_0Y_1 = 2/(πx), checking series and asymptotic regimes.
|
||
for x in [0.5, 7.0, 15.0, 24.0, 30.0, 80.0] {
|
||
let j0 = match ev("BESSELJ", vec![n(x), n(0.0)]) {
|
||
Value::Number(v) => v,
|
||
_ => panic!(),
|
||
};
|
||
let j1 = match ev("BESSELJ", vec![n(x), n(1.0)]) {
|
||
Value::Number(v) => v,
|
||
_ => panic!(),
|
||
};
|
||
let y0 = match ev("BESSELY", vec![n(x), n(0.0)]) {
|
||
Value::Number(v) => v,
|
||
_ => panic!(),
|
||
};
|
||
let y1 = match ev("BESSELY", vec![n(x), n(1.0)]) {
|
||
Value::Number(v) => v,
|
||
_ => panic!(),
|
||
};
|
||
let w = j1 * y0 - j0 * y1;
|
||
let expect = 2.0 / (std::f64::consts::PI * x);
|
||
assert!((w - expect).abs() < 1e-9 * expect.max(1.0), "x={x}: {w} vs {expect}");
|
||
}
|
||
// Large orders and arguments stay finite.
|
||
assert!(matches!(ev("BESSELJ", vec![n(50.0), n(3.0)]), Value::Number(_)));
|
||
assert!(matches!(ev("BESSELK", vec![n(30.0), n(5.0)]), Value::Number(_)));
|
||
}
|
||
}
|