Skip to main content

ganit_core/eval/functions/engineering/
mod.rs

1use super::Registry;
2use super::FunctionMeta;
3
4pub mod bitand;
5pub mod bitor;
6pub mod bitxor;
7pub mod bitlshift;
8pub mod bitrshift;
9pub mod delta;
10pub mod gestep;
11pub mod complex;
12
13pub mod bin2dec;
14pub mod bin2hex;
15pub mod bin2oct;
16pub mod dec2bin;
17pub mod dec2hex;
18pub mod dec2oct;
19pub mod hex2bin;
20pub mod hex2dec;
21pub mod hex2oct;
22pub mod oct2bin;
23pub mod oct2dec;
24pub mod oct2hex;
25
26/// Parse a binary string (up to 10 chars of 0/1) as 10-bit two's complement → i64.
27/// Returns None if invalid chars or length > 10.
28/// Empty string is treated as "0" (Google Sheets behaviour).
29pub(crate) fn parse_bin(s: &str) -> Option<i64> {
30    if s.is_empty() {
31        return Some(0);
32    }
33    if s.len() > 10 {
34        return None;
35    }
36    for c in s.chars() {
37        if c != '0' && c != '1' {
38            return None;
39        }
40    }
41    let bits = u64::from_str_radix(s, 2).ok()?;
42    // 10-bit two's complement: bit 9 is sign bit
43    if bits & 0b10_0000_0000 != 0 {
44        // negative: extend sign
45        Some(bits as i64 - 1024)
46    } else {
47        Some(bits as i64)
48    }
49}
50
51/// Parse an octal string (up to 10 chars of 0–7) as 29-bit two's complement → i64.
52/// Returns None if invalid chars or length > 10.
53/// Empty string is treated as "0" (Google Sheets behaviour).
54pub(crate) fn parse_oct(s: &str) -> Option<i64> {
55    if s.is_empty() {
56        return Some(0);
57    }
58    if s.len() > 10 {
59        return None;
60    }
61    for c in s.chars() {
62        if !('0'..='7').contains(&c) {
63            return None;
64        }
65    }
66    let bits = u64::from_str_radix(s, 8).ok()?;
67    // 29-bit two's complement: bit 29 sign (value 2^29 = 536870912)
68    if bits & 0x2000_0000 != 0 {
69        Some(bits as i64 - 0x4000_0000) // subtract 2^30
70    } else {
71        Some(bits as i64)
72    }
73}
74
75/// Parse a hex string (up to 10 chars, case-insensitive) as 40-bit two's complement → i64.
76/// Returns None if invalid chars or length > 10.
77/// Empty string is treated as "0" (Google Sheets behaviour).
78pub(crate) fn parse_hex(s: &str) -> Option<i64> {
79    if s.is_empty() {
80        return Some(0);
81    }
82    if s.len() > 10 {
83        return None;
84    }
85    for c in s.chars() {
86        if !c.is_ascii_hexdigit() {
87            return None;
88        }
89    }
90    let bits = u64::from_str_radix(s, 16).ok()?;
91    // 40-bit two's complement: bit 39 is sign (value 2^39 = 549755813888)
92    if bits & 0x80_0000_0000 != 0 {
93        Some(bits as i64 - 0x100_0000_0000i64) // subtract 2^40
94    } else {
95        Some(bits as i64)
96    }
97}
98
99/// Format i64 as binary string using 10-bit two's complement for negatives.
100/// Returns Err(()) if places is invalid or result won't fit.
101pub(crate) fn format_bin(n: i64, places: Option<usize>) -> Result<String, ()> {
102    let bits: u64 = if n < 0 {
103        // 10-bit two's complement
104        (n + 1024) as u64
105    } else {
106        n as u64
107    };
108    let s = format!("{:b}", bits);
109    apply_places(s, places, 10)
110}
111
112/// Format i64 as octal string using 10-digit two's complement for negatives.
113/// Returns Err(()) if places is invalid or result won't fit.
114pub(crate) fn format_oct(n: i64, places: Option<usize>) -> Result<String, ()> {
115    let bits: u64 = if n < 0 {
116        // 29-bit two's complement stored in 30 bits
117        (n + 0x4000_0000) as u64
118    } else {
119        n as u64
120    };
121    let s = format!("{:o}", bits);
122    apply_places(s, places, 10)
123}
124
125/// Format i64 as uppercase hex string using 10-digit two's complement for negatives.
126/// Returns Err(()) if places is invalid or result won't fit.
127pub(crate) fn format_hex(n: i64, places: Option<usize>) -> Result<String, ()> {
128    let bits: u64 = if n < 0 {
129        // 40-bit two's complement
130        (n + 0x100_0000_0000i64) as u64
131    } else {
132        n as u64
133    };
134    let s = format!("{:X}", bits);
135    apply_places(s, places, 10)
136}
137
138fn apply_places(s: String, places: Option<usize>, max_len: usize) -> Result<String, ()> {
139    match places {
140        None => Ok(s),
141        Some(p) => {
142            if p == 0 || s.len() > p || p > max_len {
143                Err(())
144            } else {
145                Ok(format!("{:0>width$}", s, width = p))
146            }
147        }
148    }
149}
150
151/// Extract optional places argument (2nd arg) from args slice.
152/// Returns Ok(None) if not provided, Ok(Some(n)) if valid positive integer, Err(Value) on error.
153pub(crate) fn get_places(args: &[crate::types::Value]) -> Result<Option<usize>, crate::types::Value> {
154    use crate::eval::coercion::to_number;
155    use crate::types::{ErrorKind, Value};
156    if args.len() < 2 {
157        return Ok(None);
158    }
159    let n = to_number(args[1].clone())?;
160    let p = n.trunc() as i64;
161    if p <= 0 {
162        return Err(Value::Error(ErrorKind::Num));
163    }
164    Ok(Some(p as usize))
165}
166
167pub fn register_engineering(registry: &mut Registry) {
168    registry.register_eager("BITAND",    bitand::bitand_fn,       FunctionMeta { category: "engineering", signature: "BITAND(number1, number2)",          description: "Bitwise AND of two integers" });
169    registry.register_eager("BITOR",     bitor::bitor_fn,         FunctionMeta { category: "engineering", signature: "BITOR(number1, number2)",           description: "Bitwise OR of two integers" });
170    registry.register_eager("BITXOR",    bitxor::bitxor_fn,       FunctionMeta { category: "engineering", signature: "BITXOR(number1, number2)",          description: "Bitwise XOR of two integers" });
171    registry.register_eager("BITLSHIFT", bitlshift::bitlshift_fn, FunctionMeta { category: "engineering", signature: "BITLSHIFT(number, shift_amount)",   description: "Left-shift an integer by a number of bits" });
172    registry.register_eager("BITRSHIFT", bitrshift::bitrshift_fn, FunctionMeta { category: "engineering", signature: "BITRSHIFT(number, shift_amount)",   description: "Right-shift an integer by a number of bits" });
173    registry.register_eager("DELTA",     delta::delta_fn,         FunctionMeta { category: "engineering", signature: "DELTA(number1, [number2])",         description: "Test whether two values are equal" });
174    registry.register_eager("GESTEP",    gestep::gestep_fn,       FunctionMeta { category: "engineering", signature: "GESTEP(number, [step])",            description: "Test whether a number is greater than or equal to a step value" });
175    registry.register_eager("BIN2DEC", bin2dec::bin2dec_fn, FunctionMeta { category: "engineering", signature: "BIN2DEC(number)",           description: "Convert binary to decimal" });
176    registry.register_eager("BIN2HEX", bin2hex::bin2hex_fn, FunctionMeta { category: "engineering", signature: "BIN2HEX(number, [places])", description: "Convert binary to hexadecimal" });
177    registry.register_eager("BIN2OCT", bin2oct::bin2oct_fn, FunctionMeta { category: "engineering", signature: "BIN2OCT(number, [places])", description: "Convert binary to octal" });
178    registry.register_eager("DEC2BIN", dec2bin::dec2bin_fn, FunctionMeta { category: "engineering", signature: "DEC2BIN(number, [places])", description: "Convert decimal to binary" });
179    registry.register_eager("DEC2HEX", dec2hex::dec2hex_fn, FunctionMeta { category: "engineering", signature: "DEC2HEX(number, [places])", description: "Convert decimal to hexadecimal" });
180    registry.register_eager("DEC2OCT", dec2oct::dec2oct_fn, FunctionMeta { category: "engineering", signature: "DEC2OCT(number, [places])", description: "Convert decimal to octal" });
181    registry.register_eager("HEX2BIN", hex2bin::hex2bin_fn, FunctionMeta { category: "engineering", signature: "HEX2BIN(number, [places])", description: "Convert hexadecimal to binary" });
182    registry.register_eager("HEX2DEC", hex2dec::hex2dec_fn, FunctionMeta { category: "engineering", signature: "HEX2DEC(number)",           description: "Convert hexadecimal to decimal" });
183    registry.register_eager("HEX2OCT", hex2oct::hex2oct_fn, FunctionMeta { category: "engineering", signature: "HEX2OCT(number, [places])", description: "Convert hexadecimal to octal" });
184    registry.register_eager("OCT2BIN", oct2bin::oct2bin_fn, FunctionMeta { category: "engineering", signature: "OCT2BIN(number, [places])", description: "Convert octal to binary" });
185    registry.register_eager("OCT2DEC", oct2dec::oct2dec_fn, FunctionMeta { category: "engineering", signature: "OCT2DEC(number)",           description: "Convert octal to decimal" });
186    registry.register_eager("OCT2HEX", oct2hex::oct2hex_fn, FunctionMeta { category: "engineering", signature: "OCT2HEX(number, [places])", description: "Convert octal to hexadecimal" });
187
188    // Complex number functions
189    registry.register_eager("COMPLEX",      complex::complex_fn,      FunctionMeta { category: "engineering", signature: "COMPLEX(real, imaginary, [suffix])", description: "Create a complex number string" });
190    registry.register_eager("IMREAL",       complex::imreal_fn,       FunctionMeta { category: "engineering", signature: "IMREAL(complex)",                   description: "Real part of a complex number" });
191    registry.register_eager("IMAGINARY",    complex::imaginary_fn,    FunctionMeta { category: "engineering", signature: "IMAGINARY(complex)",                description: "Imaginary part of a complex number" });
192    registry.register_eager("IMABS",        complex::imabs_fn,        FunctionMeta { category: "engineering", signature: "IMABS(complex)",                    description: "Absolute value of a complex number" });
193    registry.register_eager("IMPRODUCT",    complex::improduct_fn,    FunctionMeta { category: "engineering", signature: "IMPRODUCT(complex1, ...)",          description: "Product of complex numbers" });
194    registry.register_eager("IMSUB",        complex::imsub_fn,        FunctionMeta { category: "engineering", signature: "IMSUB(complex1, complex2)",         description: "Subtract complex numbers" });
195    registry.register_eager("IMSUM",        complex::imsum_fn,        FunctionMeta { category: "engineering", signature: "IMSUM(complex1, ...)",              description: "Sum of complex numbers" });
196    registry.register_eager("IMDIV",        complex::imdiv_fn,        FunctionMeta { category: "engineering", signature: "IMDIV(complex1, complex2)",         description: "Divide complex numbers" });
197    registry.register_eager("IMCONJUGATE",  complex::imconjugate_fn,  FunctionMeta { category: "engineering", signature: "IMCONJUGATE(complex)",             description: "Complex conjugate" });
198    registry.register_eager("IMARGUMENT",   complex::imargument_fn,   FunctionMeta { category: "engineering", signature: "IMARGUMENT(complex)",              description: "Argument (angle) of a complex number" });
199    registry.register_eager("IMLN",         complex::imln_fn,         FunctionMeta { category: "engineering", signature: "IMLN(complex)",                    description: "Natural log of a complex number" });
200    registry.register_eager("IMLOG10",      complex::imlog10_fn,      FunctionMeta { category: "engineering", signature: "IMLOG10(complex)",                 description: "Base-10 log of a complex number" });
201    registry.register_eager("IMLOG2",       complex::imlog2_fn,       FunctionMeta { category: "engineering", signature: "IMLOG2(complex)",                  description: "Base-2 log of a complex number" });
202    registry.register_eager("IMLOG",        complex::imlog_fn,        FunctionMeta { category: "engineering", signature: "IMLOG(complex, base)",             description: "Logarithm of a complex number to a given base" });
203    registry.register_eager("IMEXP",        complex::imexp_fn,        FunctionMeta { category: "engineering", signature: "IMEXP(complex)",                   description: "e raised to a complex power" });
204    registry.register_eager("IMPOWER",      complex::impower_fn,      FunctionMeta { category: "engineering", signature: "IMPOWER(complex, number)",         description: "Complex number raised to a power" });
205    registry.register_eager("IMSQRT",       complex::imsqrt_fn,       FunctionMeta { category: "engineering", signature: "IMSQRT(complex)",                  description: "Square root of a complex number" });
206    registry.register_eager("IMSIN",        complex::imsin_fn,        FunctionMeta { category: "engineering", signature: "IMSIN(complex)",                   description: "Sine of a complex number" });
207    registry.register_eager("IMCOS",        complex::imcos_fn,        FunctionMeta { category: "engineering", signature: "IMCOS(complex)",                   description: "Cosine of a complex number" });
208    registry.register_eager("IMTAN",        complex::imtan_fn,        FunctionMeta { category: "engineering", signature: "IMTAN(complex)",                   description: "Tangent of a complex number" });
209    registry.register_eager("IMCOT",        complex::imcot_fn,        FunctionMeta { category: "engineering", signature: "IMCOT(complex)",                   description: "Cotangent of a complex number" });
210    registry.register_eager("IMCSC",        complex::imcsc_fn,        FunctionMeta { category: "engineering", signature: "IMCSC(complex)",                   description: "Cosecant of a complex number" });
211    registry.register_eager("IMSEC",        complex::imsec_fn,        FunctionMeta { category: "engineering", signature: "IMSEC(complex)",                   description: "Secant of a complex number" });
212    registry.register_eager("IMSINH",       complex::imsinh_fn,       FunctionMeta { category: "engineering", signature: "IMSINH(complex)",                  description: "Hyperbolic sine of a complex number" });
213    registry.register_eager("IMCOSH",       complex::imcosh_fn,       FunctionMeta { category: "engineering", signature: "IMCOSH(complex)",                  description: "Hyperbolic cosine of a complex number" });
214    registry.register_eager("IMTANH",       complex::imtanh_fn,       FunctionMeta { category: "engineering", signature: "IMTANH(complex)",                  description: "Hyperbolic tangent of a complex number" });
215    registry.register_eager("IMCOTH",       complex::imcoth_fn,       FunctionMeta { category: "engineering", signature: "IMCOTH(complex)",                  description: "Hyperbolic cotangent of a complex number" });
216    registry.register_eager("IMCSCH",       complex::imcsch_fn,       FunctionMeta { category: "engineering", signature: "IMCSCH(complex)",                  description: "Hyperbolic cosecant of a complex number" });
217    registry.register_eager("IMSECH",       complex::imsech_fn,       FunctionMeta { category: "engineering", signature: "IMSECH(complex)",                  description: "Hyperbolic secant of a complex number" });
218
219    // Error functions
220    registry.register_eager("ERF",          erf::erf_fn,          FunctionMeta { category: "engineering", signature: "ERF(lower_limit, [upper_limit])", description: "Error function" });
221    registry.register_eager("ERF.PRECISE",  erf::erf_precise_fn,  FunctionMeta { category: "engineering", signature: "ERF.PRECISE(x)",                 description: "Error function (precise)" });
222    registry.register_eager("ERFC",         erf::erfc_fn,         FunctionMeta { category: "engineering", signature: "ERFC(x)",                        description: "Complementary error function" });
223    registry.register_eager("ERFC.PRECISE", erf::erfc_precise_fn, FunctionMeta { category: "engineering", signature: "ERFC.PRECISE(x)",               description: "Complementary error function (precise)" });
224}
225
226pub mod erf;