refactor(functions): move math.rs items to math/

This commit is contained in:
aiongg
2026-10-26 20:43:39 +00:00
parent be18ee0519
commit 1db55e4e5f
4 changed files with 439 additions and 423 deletions
+8 -423
View File
@@ -11,6 +11,14 @@ use crate::util::{
};
use crate::{Arg, Ctx, FnSpec, VAR};
mod criteria;
mod matrix;
mod roman_base;
use criteria::{averageif, averageifs, countif, countifs, maxifs, minifs, sumif, sumifs};
use matrix::{mdeterm, minverse, mmult, munit};
use roman_base::{arabic, base, decimal, roman};
// ---------------------------------------------------------------------------------------------
// Lifting tables
@@ -635,307 +643,6 @@ fn radians(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
unary(a, |x| Ok(x * PI / 180.0))
}
// ---------------------------------------------------------------------------------------------
// Number systems
const NUMERALS: [(char, u32); 7] = [('I', 1), ('V', 5), ('X', 10), ('L', 50), ('C', 100), ('D', 500), ('M', 1000)];
/// Tokens (text, value) for a ROMAN form, largest value first. Form 0 is classic; each higher
/// form widens one of two limits on the subtracted numeral's ratio to the larger one, alternating:
/// the limit for I/X/C (10, 10, 100, 100, 1000) and for V/L (none, 20, 20, 200, 200). So form 2
/// allows IC and XM but not VD, and form 3 adds VD and VM but not ID.
fn roman_tokens(form: u32) -> Vec<(String, u32)> {
let (pow10_max, five_max) = [(10, 0), (10, 20), (100, 20), (100, 200), (1000, 200)].get(form as usize).copied().unwrap_or((10, 0));
let mut toks: Vec<(String, u32)> = NUMERALS.iter().map(|(c, v)| (c.to_string(), *v)).collect();
for (i, (sc, sv)) in NUMERALS.iter().enumerate() {
for (lc, lv) in NUMERALS.iter().skip(i + 1) {
let ratio = lv / sv;
let pow10 = matches!(sc, 'I' | 'X' | 'C');
let ok = ratio <= if pow10 { pow10_max } else { five_max };
let value = lv - sv;
// Skip pairs that equal a single numeral (VX = V) – they are never shorter.
if ok && !NUMERALS.iter().any(|(_, v)| *v == value) {
toks.push((format!("{sc}{lc}"), value));
}
}
}
// Largest value first; on ties prefer the shorter / classic token.
toks.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.len().cmp(&b.0.len())));
toks
}
fn roman(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let n = num(a, 0)?.trunc();
let form = match a.get(1).map(|x| x.value.scalar()) {
None => 0.0,
Some(Value::Bool(true)) => 0.0,
Some(Value::Bool(false)) => 4.0,
Some(_) => num(a, 1)?.trunc(),
};
if !(0.0..=3999.0).contains(&n) || !(0.0..=4.0).contains(&form) {
return Err(CellError::Value);
}
let toks = roman_tokens(form as u32);
let mut rem = n as u32;
let mut out = String::new();
while rem > 0 {
let Some((t, v)) = toks.iter().find(|(_, v)| *v <= rem) else { break };
out.push_str(t);
rem -= v;
}
text_val(out)
}
fn arabic(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let s = text(a, 0)?;
let s = s.trim();
if s.chars().count() > 255 {
return Err(CellError::Value);
}
let (neg, body) = match s.strip_prefix('-') {
Some(r) => (true, r),
None => (false, s),
};
let mut vals = Vec::new();
for ch in body.chars() {
let u = ch.to_ascii_uppercase();
let Some((_, v)) = NUMERALS.iter().find(|(c, _)| *c == u) else { return Err(CellError::Value) };
vals.push(*v as i64);
}
let mut total = 0i64;
for (i, v) in vals.iter().enumerate() {
if vals.get(i + 1).is_some_and(|next| next > v) {
total -= v;
} else {
total += v;
}
}
num_val(if neg { -total as f64 } else { total as f64 })
}
fn base(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let n = num(a, 0)?.trunc();
let radix = num(a, 1)?.trunc();
let min_len = opt_num(a, 2, 0.0)?.trunc();
if !(0.0..9.007_199_254_740_992e15).contains(&n) || !(2.0..=36.0).contains(&radix) || !(0.0..=255.0).contains(&min_len) {
return Err(CellError::Num);
}
let radix = radix as u64;
let mut v = n as u64;
let mut digits = Vec::new();
loop {
let d = (v % radix) as u32;
digits.push(char::from_digit(d, radix as u32).unwrap_or('0').to_ascii_uppercase());
v /= radix;
if v == 0 {
break;
}
}
while digits.len() < min_len as usize {
digits.push('0');
}
text_val(digits.iter().rev().collect::<String>())
}
fn decimal(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let s = text(a, 0)?;
let radix = num(a, 1)?.trunc();
if !(2.0..=36.0).contains(&radix) || s.chars().count() > 255 {
return Err(CellError::Num);
}
let radix = radix as u32;
let mut total = 0f64;
for ch in s.trim().chars() {
let Some(d) = ch.to_digit(radix) else { return Err(CellError::Num) };
total = total * radix as f64 + d as f64;
}
if total >= 9.007_199_254_740_992e15 {
return Err(CellError::Num);
}
num_val(total)
}
// ---------------------------------------------------------------------------------------------
// Matrices
/// A numeric matrix (every element must be a number, else `#VALUE!`).
fn matrix(v: &Value) -> R<(usize, usize, Vec<f64>)> {
let arr = as_array(v);
let mut out = Vec::with_capacity(arr.data.len());
for x in arr.iter() {
match x {
Value::Number(n) => out.push(*n),
Value::Error(e) => return Err(*e),
_ => return Err(CellError::Value),
}
}
Ok((arr.rows, arr.cols, out))
}
fn to_value_array(rows: usize, cols: usize, data: Vec<f64>) -> R<Value> {
let vals = data.into_iter().map(|x| if x.is_finite() { Value::Number(x) } else { Value::Error(CellError::Num) }).collect();
array_val(rows, cols, vals)
}
fn mmult(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (ar, ac, ad) = matrix(&crate::util::arg(a, 0)?.value)?;
let (br, bc, bd) = matrix(&crate::util::arg(a, 1)?.value)?;
if ac != br {
return Err(CellError::Value);
}
if ar.saturating_mul(bc) > MAX_CELLS || ar.saturating_mul(bc).saturating_mul(ac) > 2_000_000_000 {
return Err(CellError::Num);
}
let mut out = vec![0.0; ar * bc];
for i in 0..ar {
for k in 0..ac {
let x = ad.get(i * ac + k).copied().unwrap_or(0.0);
if x == 0.0 {
continue;
}
for j in 0..bc {
if let (Some(o), Some(y)) = (out.get_mut(i * bc + j), bd.get(k * bc + j)) {
*o += x * y;
}
}
}
}
to_value_array(ar, bc, out)
}
/// LU decomposition with partial pivoting; returns (lu, perm, sign) or None when singular.
fn square(a: &[Arg]) -> R<(usize, Vec<f64>)> {
let (r, c, d) = matrix(&crate::util::arg(a, 0)?.value)?;
if r != c {
return Err(CellError::Value);
}
if r > 2000 {
return Err(CellError::Num);
}
Ok((r, d))
}
fn determinant(n: usize, mut m: Vec<f64>) -> f64 {
let mut det = 1.0;
for col in 0..n {
let mut piv = col;
let mut best = 0.0;
for row in col..n {
let v = m.get(row * n + col).copied().unwrap_or(0.0).abs();
if v > best {
best = v;
piv = row;
}
}
if best == 0.0 {
return 0.0;
}
if piv != col {
for k in 0..n {
m.swap(piv * n + k, col * n + k);
}
det = -det;
}
let p = m.get(col * n + col).copied().unwrap_or(0.0);
det *= p;
for row in col + 1..n {
let f = m.get(row * n + col).copied().unwrap_or(0.0) / p;
if f == 0.0 {
continue;
}
for k in col..n {
let v = m.get(col * n + k).copied().unwrap_or(0.0);
if let Some(x) = m.get_mut(row * n + k) {
*x -= f * v;
}
}
}
}
det
}
fn mdeterm(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (n, m) = square(a)?;
let d = determinant(n, m);
// Tidy results that should be integers (e.g. 0.9999999999999998 → 1).
num_val(if d.abs() > 1e-300 { to_sig_digits(d, 15) } else { d })
}
fn minverse(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (n, mut m) = square(a)?;
let mut inv = vec![0.0; n * n];
for i in 0..n {
if let Some(x) = inv.get_mut(i * n + i) {
*x = 1.0;
}
}
let scale = m.iter().fold(0.0f64, |acc, x| acc.max(x.abs())).max(1e-300);
for col in 0..n {
let mut piv = col;
let mut best = 0.0;
for row in col..n {
let v = m.get(row * n + col).copied().unwrap_or(0.0).abs();
if v > best {
best = v;
piv = row;
}
}
if best <= scale * 1e-15 {
return Err(CellError::Num);
}
if piv != col {
for k in 0..n {
m.swap(piv * n + k, col * n + k);
inv.swap(piv * n + k, col * n + k);
}
}
let p = m.get(col * n + col).copied().unwrap_or(1.0);
for k in 0..n {
if let Some(x) = m.get_mut(col * n + k) {
*x /= p;
}
if let Some(x) = inv.get_mut(col * n + k) {
*x /= p;
}
}
for row in 0..n {
if row == col {
continue;
}
let f = m.get(row * n + col).copied().unwrap_or(0.0);
if f == 0.0 {
continue;
}
for k in 0..n {
let mv = m.get(col * n + k).copied().unwrap_or(0.0);
let iv = inv.get(col * n + k).copied().unwrap_or(0.0);
if let Some(x) = m.get_mut(row * n + k) {
*x -= f * mv;
}
if let Some(x) = inv.get_mut(row * n + k) {
*x -= f * iv;
}
}
}
}
// Rounding noise in the inverse scales with 1/scale of the input matrix.
let noise = 1e-15 / scale;
let inv = inv.into_iter().map(|x| if x.abs() < noise { 0.0 } else { x }).collect();
to_value_array(n, n, inv)
}
fn munit(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let n = num(a, 0)?.trunc();
let (r, _) = check_size(n, n)?;
let mut data = vec![Value::Number(0.0); r * r];
for i in 0..r {
if let Some(x) = data.get_mut(i * r + i) {
*x = Value::Number(1.0);
}
}
array_val(r, r, data)
}
fn seriessum(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (x, n, m) = (num(a, 0)?, num(a, 1)?, num(a, 2)?);
let coeffs = as_array(&crate::util::arg(a, 3)?.value);
@@ -951,128 +658,6 @@ fn seriessum(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val(total)
}
// ---------------------------------------------------------------------------------------------
// Criteria aggregates
/// Values at the matched positions of `target` for every (range, criteria) pair.
fn criteria_mask(a: &[Arg], first_pair: usize, rows: usize, cols: usize) -> R<Vec<bool>> {
let rest = a.get(first_pair..).unwrap_or(&[]);
if rest.is_empty() || rest.len() % 2 != 0 {
return Err(CellError::Value);
}
// A text criterion over 255 characters is `#VALUE!` only when there is a single criteria
// pair. With two or more pairs Excel matches its first 255 characters instead.
let single_pair = rest.len() == 2;
let mut mask = vec![true; rows.saturating_mul(cols)];
for pair in rest.chunks(2) {
let (Some(range), Some(crit)) = (pair.first(), pair.get(1)) else { return Err(CellError::Value) };
let r = as_array(&range.value);
if r.rows != rows || r.cols != cols {
return Err(CellError::Value);
}
let scalar = crit.value.scalar();
let c = if single_pair { Criterion::parse_arg(&scalar)? } else { Criterion::parse_arg_truncated(&scalar) };
for (m, v) in mask.iter_mut().zip(r.iter()) {
if *m && !c.matches(v) {
*m = false;
}
}
}
Ok(mask)
}
/// Numbers in `target` where `mask` is set (errors at matched positions propagate).
fn masked_numbers(target: &Array, mask: &[bool]) -> R<Vec<f64>> {
let mut out = Vec::new();
for (v, m) in target.iter().zip(mask.iter()) {
if !*m {
continue;
}
match v {
Value::Number(n) => out.push(*n),
Value::Error(e) => return Err(*e),
_ => {}
}
}
Ok(out)
}
fn countifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let first = as_array(&crate::util::arg(a, 0)?.value);
let mask = criteria_mask(a, 0, first.rows, first.cols)?;
num_val(mask.iter().filter(|m| **m).count() as f64)
}
/// The values of the `*IFS` target range with the mask from its pairs.
fn ifs_numbers(a: &[Arg]) -> R<Vec<f64>> {
let target = as_array(&crate::util::arg(a, 0)?.value);
let mask = criteria_mask(a, 1, target.rows, target.cols)?;
masked_numbers(&target, &mask)
}
fn sumifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val(ifs_numbers(a)?.iter().sum())
}
fn averageifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = ifs_numbers(a)?;
if v.is_empty() {
return Err(CellError::Div0);
}
num_val(v.iter().sum::<f64>() / v.len() as f64)
}
fn maxifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = ifs_numbers(a)?;
num_val(v.iter().copied().reduce(f64::max).unwrap_or(0.0))
}
fn minifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = ifs_numbers(a)?;
num_val(v.iter().copied().reduce(f64::min).unwrap_or(0.0))
}
fn countif(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let range = as_array(&crate::util::arg(a, 0)?.value);
let c = Criterion::parse_arg(&crate::util::arg(a, 1)?.value.scalar())?;
num_val(range.iter().filter(|v| c.matches(v)).count() as f64)
}
/// SUMIF / AVERAGEIF: matched numbers from the sum range, read at the criteria range's positions.
/// Excel sizes a sum range reference like the criteria range, anchored at its own top-left cell;
/// the evaluator does that resizing (it has the references), so cells line up one to one here.
fn if_numbers(a: &[Arg]) -> R<Vec<f64>> {
let range = as_array(&crate::util::arg(a, 0)?.value);
let c = Criterion::parse_arg(&crate::util::arg(a, 1)?.value.scalar())?;
let sum_range = if has(a, 2) { Some(as_array(&crate::util::arg(a, 2)?.value)) } else { None };
let mut out = Vec::new();
for r in 0..range.rows {
for col in 0..range.cols {
let Some(v) = range.get(r, col) else { continue };
if !c.matches(v) {
continue;
}
let target = match &sum_range {
Some(s) => s.get(r, col),
None => Some(v),
};
match target {
Some(Value::Number(n)) => out.push(*n),
Some(Value::Error(e)) => return Err(*e),
_ => {}
}
}
}
Ok(out)
}
fn sumif(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val(if_numbers(a)?.iter().sum())
}
fn averageif(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = if_numbers(a)?;
if v.is_empty() {
return Err(CellError::Div0);
}
num_val(v.iter().sum::<f64>() / v.len() as f64)
}
// ---------------------------------------------------------------------------------------------
// Registry
+128
View File
@@ -0,0 +1,128 @@
use super::*;
// ---------------------------------------------------------------------------------------------
// Criteria aggregates
/// Values at the matched positions of `target` for every (range, criteria) pair.
fn criteria_mask(a: &[Arg], first_pair: usize, rows: usize, cols: usize) -> R<Vec<bool>> {
let rest = a.get(first_pair..).unwrap_or(&[]);
if rest.is_empty() || rest.len() % 2 != 0 {
return Err(CellError::Value);
}
// A text criterion over 255 characters is `#VALUE!` only when there is a single criteria
// pair. With two or more pairs Excel matches its first 255 characters instead.
let single_pair = rest.len() == 2;
let mut mask = vec![true; rows.saturating_mul(cols)];
for pair in rest.chunks(2) {
let (Some(range), Some(crit)) = (pair.first(), pair.get(1)) else { return Err(CellError::Value) };
let r = as_array(&range.value);
if r.rows != rows || r.cols != cols {
return Err(CellError::Value);
}
let scalar = crit.value.scalar();
let c = if single_pair { Criterion::parse_arg(&scalar)? } else { Criterion::parse_arg_truncated(&scalar) };
for (m, v) in mask.iter_mut().zip(r.iter()) {
if *m && !c.matches(v) {
*m = false;
}
}
}
Ok(mask)
}
/// Numbers in `target` where `mask` is set (errors at matched positions propagate).
fn masked_numbers(target: &Array, mask: &[bool]) -> R<Vec<f64>> {
let mut out = Vec::new();
for (v, m) in target.iter().zip(mask.iter()) {
if !*m {
continue;
}
match v {
Value::Number(n) => out.push(*n),
Value::Error(e) => return Err(*e),
_ => {}
}
}
Ok(out)
}
pub(super) fn countifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let first = as_array(&crate::util::arg(a, 0)?.value);
let mask = criteria_mask(a, 0, first.rows, first.cols)?;
num_val(mask.iter().filter(|m| **m).count() as f64)
}
/// The values of the `*IFS` target range with the mask from its pairs.
fn ifs_numbers(a: &[Arg]) -> R<Vec<f64>> {
let target = as_array(&crate::util::arg(a, 0)?.value);
let mask = criteria_mask(a, 1, target.rows, target.cols)?;
masked_numbers(&target, &mask)
}
pub(super) fn sumifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val(ifs_numbers(a)?.iter().sum())
}
pub(super) fn averageifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = ifs_numbers(a)?;
if v.is_empty() {
return Err(CellError::Div0);
}
num_val(v.iter().sum::<f64>() / v.len() as f64)
}
pub(super) fn maxifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = ifs_numbers(a)?;
num_val(v.iter().copied().reduce(f64::max).unwrap_or(0.0))
}
pub(super) fn minifs(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = ifs_numbers(a)?;
num_val(v.iter().copied().reduce(f64::min).unwrap_or(0.0))
}
pub(super) fn countif(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let range = as_array(&crate::util::arg(a, 0)?.value);
let c = Criterion::parse_arg(&crate::util::arg(a, 1)?.value.scalar())?;
num_val(range.iter().filter(|v| c.matches(v)).count() as f64)
}
/// SUMIF / AVERAGEIF: matched numbers from the sum range, read at the criteria range's positions.
/// Excel sizes a sum range reference like the criteria range, anchored at its own top-left cell;
/// the evaluator does that resizing (it has the references), so cells line up one to one here.
fn if_numbers(a: &[Arg]) -> R<Vec<f64>> {
let range = as_array(&crate::util::arg(a, 0)?.value);
let c = Criterion::parse_arg(&crate::util::arg(a, 1)?.value.scalar())?;
let sum_range = if has(a, 2) { Some(as_array(&crate::util::arg(a, 2)?.value)) } else { None };
let mut out = Vec::new();
for r in 0..range.rows {
for col in 0..range.cols {
let Some(v) = range.get(r, col) else { continue };
if !c.matches(v) {
continue;
}
let target = match &sum_range {
Some(s) => s.get(r, col),
None => Some(v),
};
match target {
Some(Value::Number(n)) => out.push(*n),
Some(Value::Error(e)) => return Err(*e),
_ => {}
}
}
}
Ok(out)
}
pub(super) fn sumif(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val(if_numbers(a)?.iter().sum())
}
pub(super) fn averageif(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let v = if_numbers(a)?;
if v.is_empty() {
return Err(CellError::Div0);
}
num_val(v.iter().sum::<f64>() / v.len() as f64)
}
+182
View File
@@ -0,0 +1,182 @@
use super::*;
// ---------------------------------------------------------------------------------------------
// Matrices
/// A numeric matrix (every element must be a number, else `#VALUE!`).
fn matrix(v: &Value) -> R<(usize, usize, Vec<f64>)> {
let arr = as_array(v);
let mut out = Vec::with_capacity(arr.data.len());
for x in arr.iter() {
match x {
Value::Number(n) => out.push(*n),
Value::Error(e) => return Err(*e),
_ => return Err(CellError::Value),
}
}
Ok((arr.rows, arr.cols, out))
}
fn to_value_array(rows: usize, cols: usize, data: Vec<f64>) -> R<Value> {
let vals = data.into_iter().map(|x| if x.is_finite() { Value::Number(x) } else { Value::Error(CellError::Num) }).collect();
array_val(rows, cols, vals)
}
pub(super) fn mmult(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (ar, ac, ad) = matrix(&crate::util::arg(a, 0)?.value)?;
let (br, bc, bd) = matrix(&crate::util::arg(a, 1)?.value)?;
if ac != br {
return Err(CellError::Value);
}
if ar.saturating_mul(bc) > MAX_CELLS || ar.saturating_mul(bc).saturating_mul(ac) > 2_000_000_000 {
return Err(CellError::Num);
}
let mut out = vec![0.0; ar * bc];
for i in 0..ar {
for k in 0..ac {
let x = ad.get(i * ac + k).copied().unwrap_or(0.0);
if x == 0.0 {
continue;
}
for j in 0..bc {
if let (Some(o), Some(y)) = (out.get_mut(i * bc + j), bd.get(k * bc + j)) {
*o += x * y;
}
}
}
}
to_value_array(ar, bc, out)
}
/// LU decomposition with partial pivoting; returns (lu, perm, sign) or None when singular.
fn square(a: &[Arg]) -> R<(usize, Vec<f64>)> {
let (r, c, d) = matrix(&crate::util::arg(a, 0)?.value)?;
if r != c {
return Err(CellError::Value);
}
if r > 2000 {
return Err(CellError::Num);
}
Ok((r, d))
}
fn determinant(n: usize, mut m: Vec<f64>) -> f64 {
let mut det = 1.0;
for col in 0..n {
let mut piv = col;
let mut best = 0.0;
for row in col..n {
let v = m.get(row * n + col).copied().unwrap_or(0.0).abs();
if v > best {
best = v;
piv = row;
}
}
if best == 0.0 {
return 0.0;
}
if piv != col {
for k in 0..n {
m.swap(piv * n + k, col * n + k);
}
det = -det;
}
let p = m.get(col * n + col).copied().unwrap_or(0.0);
det *= p;
for row in col + 1..n {
let f = m.get(row * n + col).copied().unwrap_or(0.0) / p;
if f == 0.0 {
continue;
}
for k in col..n {
let v = m.get(col * n + k).copied().unwrap_or(0.0);
if let Some(x) = m.get_mut(row * n + k) {
*x -= f * v;
}
}
}
}
det
}
pub(super) fn mdeterm(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (n, m) = square(a)?;
let d = determinant(n, m);
// Tidy results that should be integers (e.g. 0.9999999999999998 → 1).
num_val(if d.abs() > 1e-300 { to_sig_digits(d, 15) } else { d })
}
pub(super) fn minverse(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let (n, mut m) = square(a)?;
let mut inv = vec![0.0; n * n];
for i in 0..n {
if let Some(x) = inv.get_mut(i * n + i) {
*x = 1.0;
}
}
let scale = m.iter().fold(0.0f64, |acc, x| acc.max(x.abs())).max(1e-300);
for col in 0..n {
let mut piv = col;
let mut best = 0.0;
for row in col..n {
let v = m.get(row * n + col).copied().unwrap_or(0.0).abs();
if v > best {
best = v;
piv = row;
}
}
if best <= scale * 1e-15 {
return Err(CellError::Num);
}
if piv != col {
for k in 0..n {
m.swap(piv * n + k, col * n + k);
inv.swap(piv * n + k, col * n + k);
}
}
let p = m.get(col * n + col).copied().unwrap_or(1.0);
for k in 0..n {
if let Some(x) = m.get_mut(col * n + k) {
*x /= p;
}
if let Some(x) = inv.get_mut(col * n + k) {
*x /= p;
}
}
for row in 0..n {
if row == col {
continue;
}
let f = m.get(row * n + col).copied().unwrap_or(0.0);
if f == 0.0 {
continue;
}
for k in 0..n {
let mv = m.get(col * n + k).copied().unwrap_or(0.0);
let iv = inv.get(col * n + k).copied().unwrap_or(0.0);
if let Some(x) = m.get_mut(row * n + k) {
*x -= f * mv;
}
if let Some(x) = inv.get_mut(row * n + k) {
*x -= f * iv;
}
}
}
}
// Rounding noise in the inverse scales with 1/scale of the input matrix.
let noise = 1e-15 / scale;
let inv = inv.into_iter().map(|x| if x.abs() < noise { 0.0 } else { x }).collect();
to_value_array(n, n, inv)
}
pub(super) fn munit(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let n = num(a, 0)?.trunc();
let (r, _) = check_size(n, n)?;
let mut data = vec![Value::Number(0.0); r * r];
for i in 0..r {
if let Some(x) = data.get_mut(i * r + i) {
*x = Value::Number(1.0);
}
}
array_val(r, r, data)
}
+121
View File
@@ -0,0 +1,121 @@
use super::*;
// ---------------------------------------------------------------------------------------------
// Number systems
const NUMERALS: [(char, u32); 7] = [('I', 1), ('V', 5), ('X', 10), ('L', 50), ('C', 100), ('D', 500), ('M', 1000)];
/// Tokens (text, value) for a ROMAN form, largest value first. Form 0 is classic; each higher
/// form widens one of two limits on the subtracted numeral's ratio to the larger one, alternating:
/// the limit for I/X/C (10, 10, 100, 100, 1000) and for V/L (none, 20, 20, 200, 200). So form 2
/// allows IC and XM but not VD, and form 3 adds VD and VM but not ID.
fn roman_tokens(form: u32) -> Vec<(String, u32)> {
let (pow10_max, five_max) = [(10, 0), (10, 20), (100, 20), (100, 200), (1000, 200)].get(form as usize).copied().unwrap_or((10, 0));
let mut toks: Vec<(String, u32)> = NUMERALS.iter().map(|(c, v)| (c.to_string(), *v)).collect();
for (i, (sc, sv)) in NUMERALS.iter().enumerate() {
for (lc, lv) in NUMERALS.iter().skip(i + 1) {
let ratio = lv / sv;
let pow10 = matches!(sc, 'I' | 'X' | 'C');
let ok = ratio <= if pow10 { pow10_max } else { five_max };
let value = lv - sv;
// Skip pairs that equal a single numeral (VX = V) – they are never shorter.
if ok && !NUMERALS.iter().any(|(_, v)| *v == value) {
toks.push((format!("{sc}{lc}"), value));
}
}
}
// Largest value first; on ties prefer the shorter / classic token.
toks.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.len().cmp(&b.0.len())));
toks
}
pub(super) fn roman(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let n = num(a, 0)?.trunc();
let form = match a.get(1).map(|x| x.value.scalar()) {
None => 0.0,
Some(Value::Bool(true)) => 0.0,
Some(Value::Bool(false)) => 4.0,
Some(_) => num(a, 1)?.trunc(),
};
if !(0.0..=3999.0).contains(&n) || !(0.0..=4.0).contains(&form) {
return Err(CellError::Value);
}
let toks = roman_tokens(form as u32);
let mut rem = n as u32;
let mut out = String::new();
while rem > 0 {
let Some((t, v)) = toks.iter().find(|(_, v)| *v <= rem) else { break };
out.push_str(t);
rem -= v;
}
text_val(out)
}
pub(super) fn arabic(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let s = text(a, 0)?;
let s = s.trim();
if s.chars().count() > 255 {
return Err(CellError::Value);
}
let (neg, body) = match s.strip_prefix('-') {
Some(r) => (true, r),
None => (false, s),
};
let mut vals = Vec::new();
for ch in body.chars() {
let u = ch.to_ascii_uppercase();
let Some((_, v)) = NUMERALS.iter().find(|(c, _)| *c == u) else { return Err(CellError::Value) };
vals.push(*v as i64);
}
let mut total = 0i64;
for (i, v) in vals.iter().enumerate() {
if vals.get(i + 1).is_some_and(|next| next > v) {
total -= v;
} else {
total += v;
}
}
num_val(if neg { -total as f64 } else { total as f64 })
}
pub(super) fn base(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let n = num(a, 0)?.trunc();
let radix = num(a, 1)?.trunc();
let min_len = opt_num(a, 2, 0.0)?.trunc();
if !(0.0..9.007_199_254_740_992e15).contains(&n) || !(2.0..=36.0).contains(&radix) || !(0.0..=255.0).contains(&min_len) {
return Err(CellError::Num);
}
let radix = radix as u64;
let mut v = n as u64;
let mut digits = Vec::new();
loop {
let d = (v % radix) as u32;
digits.push(char::from_digit(d, radix as u32).unwrap_or('0').to_ascii_uppercase());
v /= radix;
if v == 0 {
break;
}
}
while digits.len() < min_len as usize {
digits.push('0');
}
text_val(digits.iter().rev().collect::<String>())
}
pub(super) fn decimal(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
let s = text(a, 0)?;
let radix = num(a, 1)?.trunc();
if !(2.0..=36.0).contains(&radix) || s.chars().count() > 255 {
return Err(CellError::Num);
}
let radix = radix as u32;
let mut total = 0f64;
for ch in s.trim().chars() {
let Some(d) = ch.to_digit(radix) else { return Err(CellError::Num) };
total = total * radix as f64 + d as f64;
}
if total >= 9.007_199_254_740_992e15 {
return Err(CellError::Num);
}
num_val(total)
}