diff --git a/crates/typst-library/src/foundations/calc.rs b/crates/typst-library/src/foundations/calc.rs index f12ca74ca..e7eb14055 100644 --- a/crates/typst-library/src/foundations/calc.rs +++ b/crates/typst-library/src/foundations/calc.rs @@ -50,6 +50,7 @@ pub fn module() -> Module { scope.define_func::(); scope.define_func::(); scope.define_func::(); + scope.define_func::(); scope.define("inf", f64::INFINITY); scope.define("pi", std::f64::consts::PI); scope.define("tau", std::f64::consts::TAU); @@ -1056,6 +1057,38 @@ pub fn quo( floor(divided).at(span) } +/// Calculates the p-norm of a sequence of values. +/// +/// ```example +/// #calc.norm(1, 2, -3, 0.5) \ +/// #calc.norm(p: 3, 1, 2) +/// ``` +#[func(title = "𝑝-Norm")] +pub fn norm( + /// The p value to calculate the p-norm of. + #[named] + #[default(Spanned::new(2.0, Span::detached()))] + p: Spanned, + /// The sequence of values from which to calculate the p-norm. + /// Returns `0.0` if empty. + #[variadic] + values: Vec, +) -> SourceResult { + if p.v <= 0.0 { + bail!(p.span, "p must be greater than zero"); + } + + // Create an iterator over the absolute values. + let abs = values.into_iter().map(f64::abs); + + Ok(if p.v.is_infinite() { + // When p is infinity, the p-norm is the maximum of the absolute values. + abs.max_by(|a, b| a.total_cmp(b)).unwrap_or(0.0) + } else { + abs.map(|v| v.powf(p.v)).sum::().powf(1.0 / p.v) + }) +} + /// A value which can be passed to functions that work with integers and floats. #[derive(Debug, Copy, Clone)] pub enum Num { diff --git a/tests/suite/foundations/calc.typ b/tests/suite/foundations/calc.typ index 5726dafa1..23fdea89c 100644 --- a/tests/suite/foundations/calc.typ +++ b/tests/suite/foundations/calc.typ @@ -368,3 +368,19 @@ // Error: 2-37 cannot apply this operation to a decimal and a float // Hint: 2-37 if loss of precision is acceptable, explicitly cast the decimal to a float with `float(value)` #calc.clamp(decimal("10"), 5.5, 6.6) + +--- calc-norm --- +#test(calc.norm(1, 2, -3, 0.5), calc.sqrt(14.25)) +#test(calc.norm(3, 4), 5.0) +#test(calc.norm(3, 4), 5.0) +#test(calc.norm(), 0.0) +#test(calc.norm(p: 3, 1, -2), calc.pow(9, 1/3)) +#test(calc.norm(p: calc.inf, 1, -2), 2.0) + +--- calc-norm-negative-p --- +// Error: 15-17 p must be greater than zero +#calc.norm(p: -1, 1) + +--- calc-norm-expected-float --- +// Error: 12-15 expected float, found ratio +#calc.norm(10%)