summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/compute/calc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/compute/calc.rs')
-rw-r--r--crates/typst-library/src/compute/calc.rs1024
1 files changed, 1024 insertions, 0 deletions
diff --git a/crates/typst-library/src/compute/calc.rs b/crates/typst-library/src/compute/calc.rs
new file mode 100644
index 00000000..81715007
--- /dev/null
+++ b/crates/typst-library/src/compute/calc.rs
@@ -0,0 +1,1024 @@
+//! Calculations and processing of numeric values.
+
+use std::cmp;
+use std::cmp::Ordering;
+use std::ops::{Div, Rem};
+
+use typst::eval::{Module, Scope};
+
+use crate::prelude::*;
+
+/// A module with computational functions.
+pub fn module() -> Module {
+ let mut scope = Scope::new();
+ scope.define("abs", abs_func());
+ scope.define("pow", pow_func());
+ scope.define("exp", exp_func());
+ scope.define("sqrt", sqrt_func());
+ scope.define("sin", sin_func());
+ scope.define("cos", cos_func());
+ scope.define("tan", tan_func());
+ scope.define("asin", asin_func());
+ scope.define("acos", acos_func());
+ scope.define("atan", atan_func());
+ scope.define("atan2", atan2_func());
+ scope.define("sinh", sinh_func());
+ scope.define("cosh", cosh_func());
+ scope.define("tanh", tanh_func());
+ scope.define("log", log_func());
+ scope.define("ln", ln_func());
+ scope.define("fact", fact_func());
+ scope.define("perm", perm_func());
+ scope.define("binom", binom_func());
+ scope.define("gcd", gcd_func());
+ scope.define("lcm", lcm_func());
+ scope.define("floor", floor_func());
+ scope.define("ceil", ceil_func());
+ scope.define("trunc", trunc_func());
+ scope.define("fract", fract_func());
+ scope.define("round", round_func());
+ scope.define("clamp", clamp_func());
+ scope.define("min", min_func());
+ scope.define("max", max_func());
+ scope.define("even", even_func());
+ scope.define("odd", odd_func());
+ scope.define("rem", rem_func());
+ scope.define("quo", quo_func());
+ scope.define("inf", f64::INFINITY);
+ scope.define("nan", f64::NAN);
+ scope.define("pi", std::f64::consts::PI);
+ scope.define("e", std::f64::consts::E);
+ Module::new("calc").with_scope(scope)
+}
+
+/// Calculates the absolute value of a numeric value.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.abs(-5) \
+/// #calc.abs(5pt - 2cm) \
+/// #calc.abs(2fr)
+/// ```
+///
+/// Display: Absolute
+/// Category: calculate
+#[func]
+pub fn abs(
+ /// The value whose absolute value to calculate.
+ value: ToAbs,
+) -> Value {
+ value.0
+}
+
+/// A value of which the absolute value can be taken.
+pub struct ToAbs(Value);
+
+cast! {
+ ToAbs,
+ v: i64 => Self(v.abs().into_value()),
+ v: f64 => Self(v.abs().into_value()),
+ v: Length => Self(Value::Length(v.try_abs()
+ .ok_or("cannot take absolute value of this length")?)),
+ v: Angle => Self(Value::Angle(v.abs())),
+ v: Ratio => Self(Value::Ratio(v.abs())),
+ v: Fr => Self(Value::Fraction(v.abs())),
+}
+
+/// Raises a value to some exponent.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.pow(2, 3)
+/// ```
+///
+/// Display: Power
+/// Category: calculate
+#[func]
+pub fn pow(
+ /// The base of the power.
+ base: Num,
+ /// The exponent of the power.
+ exponent: Spanned<Num>,
+ /// The callsite span.
+ span: Span,
+) -> SourceResult<Num> {
+ match exponent.v {
+ _ if exponent.v.float() == 0.0 && base.float() == 0.0 => {
+ bail!(span, "zero to the power of zero is undefined")
+ }
+ Num::Int(i) if i32::try_from(i).is_err() => {
+ bail!(exponent.span, "exponent is too large")
+ }
+ Num::Float(f) if !f.is_normal() && f != 0.0 => {
+ bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN")
+ }
+ _ => {}
+ };
+
+ let result = match (base, exponent.v) {
+ (Num::Int(a), Num::Int(b)) if b >= 0 => a
+ .checked_pow(b as u32)
+ .map(Num::Int)
+ .ok_or("the result is too large")
+ .at(span)?,
+ (a, b) => Num::Float(if a.float() == std::f64::consts::E {
+ b.float().exp()
+ } else if a.float() == 2.0 {
+ b.float().exp2()
+ } else if let Num::Int(b) = b {
+ a.float().powi(b as i32)
+ } else {
+ a.float().powf(b.float())
+ }),
+ };
+
+ if result.float().is_nan() {
+ bail!(span, "the result is not a real number")
+ }
+
+ Ok(result)
+}
+
+/// Raises a value to some exponent of e.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.exp(1)
+/// ```
+///
+/// Display: Exponential
+/// Category: calculate
+#[func]
+pub fn exp(
+ /// The exponent of the power.
+ exponent: Spanned<Num>,
+ /// The callsite span.
+ span: Span,
+) -> SourceResult<f64> {
+ match exponent.v {
+ Num::Int(i) if i32::try_from(i).is_err() => {
+ bail!(exponent.span, "exponent is too large")
+ }
+ Num::Float(f) if !f.is_normal() && f != 0.0 => {
+ bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN")
+ }
+ _ => {}
+ };
+
+ let result = exponent.v.float().exp();
+ if result.is_nan() {
+ bail!(span, "the result is not a real number")
+ }
+
+ Ok(result)
+}
+
+/// Extracts the square root of a number.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.sqrt(16) \
+/// #calc.sqrt(2.5)
+/// ```
+///
+/// Display: Square Root
+/// Category: calculate
+#[func]
+pub fn sqrt(
+ /// The number whose square root to calculate. Must be non-negative.
+ value: Spanned<Num>,
+) -> SourceResult<f64> {
+ if value.v.float() < 0.0 {
+ bail!(value.span, "cannot take square root of negative number");
+ }
+ Ok(value.v.float().sqrt())
+}
+
+/// Calculates the sine of an angle.
+///
+/// When called with an integer or a float, they will be interpreted as
+/// radians.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.sin(90deg) == calc.sin(-270deg))
+/// #calc.sin(1.5) \
+/// #calc.sin(90deg)
+/// ```
+///
+/// Display: Sine
+/// Category: calculate
+#[func]
+pub fn sin(
+ /// The angle whose sine to calculate.
+ angle: AngleLike,
+) -> f64 {
+ match angle {
+ AngleLike::Angle(a) => a.sin(),
+ AngleLike::Int(n) => (n as f64).sin(),
+ AngleLike::Float(n) => n.sin(),
+ }
+}
+
+/// Calculates the cosine of an angle.
+///
+/// When called with an integer or a float, they will be interpreted as
+/// radians.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.cos(90deg) \
+/// #calc.cos(1.5) \
+/// #calc.cos(90deg)
+/// ```
+///
+/// Display: Cosine
+/// Category: calculate
+#[func]
+pub fn cos(
+ /// The angle whose cosine to calculate.
+ angle: AngleLike,
+) -> f64 {
+ match angle {
+ AngleLike::Angle(a) => a.cos(),
+ AngleLike::Int(n) => (n as f64).cos(),
+ AngleLike::Float(n) => n.cos(),
+ }
+}
+
+/// Calculates the tangent of an angle.
+///
+/// When called with an integer or a float, they will be interpreted as
+/// radians.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.tan(1.5) \
+/// #calc.tan(90deg)
+/// ```
+///
+/// Display: Tangent
+/// Category: calculate
+#[func]
+pub fn tan(
+ /// The angle whose tangent to calculate.
+ angle: AngleLike,
+) -> f64 {
+ match angle {
+ AngleLike::Angle(a) => a.tan(),
+ AngleLike::Int(n) => (n as f64).tan(),
+ AngleLike::Float(n) => n.tan(),
+ }
+}
+
+/// Calculates the arcsine of a number.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.asin(0) \
+/// #calc.asin(1)
+/// ```
+///
+/// Display: Arcsine
+/// Category: calculate
+#[func]
+pub fn asin(
+ /// The number whose arcsine to calculate. Must be between -1 and 1.
+ value: Spanned<Num>,
+) -> SourceResult<Angle> {
+ let val = value.v.float();
+ if val < -1.0 || val > 1.0 {
+ bail!(value.span, "value must be between -1 and 1");
+ }
+ Ok(Angle::rad(val.asin()))
+}
+
+/// Calculates the arccosine of a number.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.acos(0) \
+/// #calc.acos(1)
+/// ```
+///
+/// Display: Arccosine
+/// Category: calculate
+#[func]
+pub fn acos(
+ /// The number whose arcsine to calculate. Must be between -1 and 1.
+ value: Spanned<Num>,
+) -> SourceResult<Angle> {
+ let val = value.v.float();
+ if val < -1.0 || val > 1.0 {
+ bail!(value.span, "value must be between -1 and 1");
+ }
+ Ok(Angle::rad(val.acos()))
+}
+
+/// Calculates the arctangent of a number.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.atan(0) \
+/// #calc.atan(1)
+/// ```
+///
+/// Display: Arctangent
+/// Category: calculate
+#[func]
+pub fn atan(
+ /// The number whose arctangent to calculate.
+ value: Num,
+) -> Angle {
+ Angle::rad(value.float().atan())
+}
+
+/// Calculates the four-quadrant arctangent of a coordinate.
+///
+/// The arguments are `(x, y)`, not `(y, x)`.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.atan2(1, 1) \
+/// #calc.atan2(-2, -3)
+/// ```
+///
+/// Display: Four-quadrant Arctangent
+/// Category: calculate
+#[func]
+pub fn atan2(
+ /// The X coordinate.
+ x: Num,
+ /// The Y coordinate.
+ y: Num,
+) -> Angle {
+ Angle::rad(f64::atan2(y.float(), x.float()))
+}
+
+/// Calculates the hyperbolic sine of an angle.
+///
+/// When called with an integer or a float, they will be interpreted as radians.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.sinh(0) \
+/// #calc.sinh(45deg)
+/// ```
+///
+/// Display: Hyperbolic sine
+/// Category: calculate
+#[func]
+pub fn sinh(
+ /// The angle whose hyperbolic sine to calculate.
+ angle: AngleLike,
+) -> f64 {
+ match angle {
+ AngleLike::Angle(a) => a.to_rad().sinh(),
+ AngleLike::Int(n) => (n as f64).sinh(),
+ AngleLike::Float(n) => n.sinh(),
+ }
+}
+
+/// Calculates the hyperbolic cosine of an angle.
+///
+/// When called with an integer or a float, they will be interpreted as radians.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.cosh(0) \
+/// #calc.cosh(45deg)
+/// ```
+///
+/// Display: Hyperbolic cosine
+/// Category: calculate
+#[func]
+pub fn cosh(
+ /// The angle whose hyperbolic cosine to calculate.
+ angle: AngleLike,
+) -> f64 {
+ match angle {
+ AngleLike::Angle(a) => a.to_rad().cosh(),
+ AngleLike::Int(n) => (n as f64).cosh(),
+ AngleLike::Float(n) => n.cosh(),
+ }
+}
+
+/// Calculates the hyperbolic tangent of an angle.
+///
+/// When called with an integer or a float, they will be interpreted as radians.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.tanh(0) \
+/// #calc.tanh(45deg)
+/// ```
+///
+/// Display: Hyperbolic tangent
+/// Category: calculate
+#[func]
+pub fn tanh(
+ /// The angle whose hyperbolic tangent to calculate.
+ angle: AngleLike,
+) -> f64 {
+ match angle {
+ AngleLike::Angle(a) => a.to_rad().tanh(),
+ AngleLike::Int(n) => (n as f64).tanh(),
+ AngleLike::Float(n) => n.tanh(),
+ }
+}
+
+/// Calculates the logarithm of a number.
+///
+/// If the base is not specified, the logarithm is calculated in base 10.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.log(100)
+/// ```
+///
+/// Display: Logarithm
+/// Category: calculate
+#[func]
+pub fn log(
+ /// The number whose logarithm to calculate. Must be strictly positive.
+ value: Spanned<Num>,
+ /// The base of the logarithm. May not be zero.
+ #[named]
+ #[default(Spanned::new(10.0, Span::detached()))]
+ base: Spanned<f64>,
+ /// The callsite span.
+ span: Span,
+) -> SourceResult<f64> {
+ let number = value.v.float();
+ if number <= 0.0 {
+ bail!(value.span, "value must be strictly positive")
+ }
+
+ if !base.v.is_normal() {
+ bail!(base.span, "base may not be zero, NaN, infinite, or subnormal")
+ }
+
+ let result = if base.v == std::f64::consts::E {
+ number.ln()
+ } else if base.v == 2.0 {
+ number.log2()
+ } else if base.v == 10.0 {
+ number.log10()
+ } else {
+ number.log(base.v)
+ };
+
+ if result.is_infinite() || result.is_nan() {
+ bail!(span, "the result is not a real number")
+ }
+
+ Ok(result)
+}
+
+/// Calculates the natural logarithm of a number.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.ln(calc.e)
+/// ```
+///
+/// Display: Natural Logarithm
+/// Category: calculate
+#[func]
+pub fn ln(
+ /// The number whose logarithm to calculate. Must be strictly positive.
+ value: Spanned<Num>,
+ /// The callsite span.
+ span: Span,
+) -> SourceResult<f64> {
+ let number = value.v.float();
+ if number <= 0.0 {
+ bail!(value.span, "value must be strictly positive")
+ }
+
+ let result = number.ln();
+ if result.is_infinite() {
+ bail!(span, "result close to -inf")
+ }
+
+ Ok(result)
+}
+
+/// Calculates the factorial of a number.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.fact(5)
+/// ```
+///
+/// Display: Factorial
+/// Category: calculate
+#[func]
+pub fn fact(
+ /// The number whose factorial to calculate. Must be non-negative.
+ number: u64,
+) -> StrResult<i64> {
+ Ok(fact_impl(1, number).ok_or("the result is too large")?)
+}
+
+/// Calculates a permutation.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.perm(10, 5)
+/// ```
+///
+/// Display: Permutation
+/// Category: calculate
+#[func]
+pub fn perm(
+ /// The base number. Must be non-negative.
+ base: u64,
+ /// The number of permutations. Must be non-negative.
+ numbers: u64,
+) -> StrResult<i64> {
+ // By convention.
+ if base < numbers {
+ return Ok(0);
+ }
+
+ Ok(fact_impl(base - numbers + 1, base).ok_or("the result is too large")?)
+}
+
+/// Calculates the product of a range of numbers. Used to calculate
+/// permutations. Returns None if the result is larger than `i64::MAX`
+fn fact_impl(start: u64, end: u64) -> Option<i64> {
+ // By convention
+ if end + 1 < start {
+ return Some(0);
+ }
+
+ let real_start: u64 = cmp::max(1, start);
+ let mut count: u64 = 1;
+ for i in real_start..=end {
+ count = count.checked_mul(i)?;
+ }
+
+ count.try_into().ok()
+}
+
+/// Calculates a binomial coefficient.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.binom(10, 5)
+/// ```
+///
+/// Display: Binomial
+/// Category: calculate
+#[func]
+pub fn binom(
+ /// The upper coefficient. Must be non-negative.
+ n: u64,
+ /// The lower coefficient. Must be non-negative.
+ k: u64,
+) -> StrResult<i64> {
+ Ok(binom_impl(n, k).ok_or("the result is too large")?)
+}
+
+/// Calculates a binomial coefficient, with `n` the upper coefficient and `k`
+/// the lower coefficient. Returns `None` if the result is larger than
+/// `i64::MAX`
+fn binom_impl(n: u64, k: u64) -> Option<i64> {
+ if k > n {
+ return Some(0);
+ }
+
+ // By symmetry
+ let real_k = cmp::min(n - k, k);
+ if real_k == 0 {
+ return Some(1);
+ }
+
+ let mut result: u64 = 1;
+ for i in 0..real_k {
+ result = result.checked_mul(n - i)?.checked_div(i + 1)?;
+ }
+
+ result.try_into().ok()
+}
+
+/// Calculates the greatest common divisor of two integers.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.gcd(7, 42)
+/// ```
+///
+/// Display: Greatest Common Divisor
+/// Category: calculate
+#[func]
+pub fn gcd(
+ /// The first integer.
+ a: i64,
+ /// The second integer.
+ b: i64,
+) -> i64 {
+ let (mut a, mut b) = (a, b);
+ while b != 0 {
+ let temp = b;
+ b = a % b;
+ a = temp;
+ }
+
+ a.abs()
+}
+
+/// Calculates the least common multiple of two integers.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.lcm(96, 13)
+/// ```
+///
+/// Display: Least Common Multiple
+/// Category: calculate
+#[func]
+pub fn lcm(
+ /// The first integer.
+ a: i64,
+ /// The second integer.
+ b: i64,
+) -> StrResult<i64> {
+ if a == b {
+ return Ok(a.abs());
+ }
+
+ Ok(a.checked_div(gcd(a, b))
+ .and_then(|gcd| gcd.checked_mul(b))
+ .map(|v| v.abs())
+ .ok_or("the return value is too large")?)
+}
+
+/// Rounds a number down to the nearest integer.
+///
+/// If the number is already an integer, it is returned unchanged.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.floor(3.14) == 3)
+/// #assert(calc.floor(3) == 3)
+/// #calc.floor(500.1)
+/// ```
+///
+/// Display: Round down
+/// Category: calculate
+#[func]
+pub fn floor(
+ /// The number to round down.
+ value: Num,
+) -> i64 {
+ match value {
+ Num::Int(n) => n,
+ Num::Float(n) => n.floor() as i64,
+ }
+}
+
+/// Rounds a number up to the nearest integer.
+///
+/// If the number is already an integer, it is returned unchanged.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.ceil(3.14) == 4)
+/// #assert(calc.ceil(3) == 3)
+/// #calc.ceil(500.1)
+/// ```
+///
+/// Display: Round up
+/// Category: calculate
+#[func]
+pub fn ceil(
+ /// The number to round up.
+ value: Num,
+) -> i64 {
+ match value {
+ Num::Int(n) => n,
+ Num::Float(n) => n.ceil() as i64,
+ }
+}
+
+/// Returns the integer part of a number.
+///
+/// If the number is already an integer, it is returned unchanged.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.trunc(3) == 3)
+/// #assert(calc.trunc(-3.7) == -3)
+/// #assert(calc.trunc(15.9) == 15)
+/// ```
+///
+/// Display: Truncate
+/// Category: calculate
+#[func]
+pub fn trunc(
+ /// The number to truncate.
+ value: Num,
+) -> i64 {
+ match value {
+ Num::Int(n) => n,
+ Num::Float(n) => n.trunc() as i64,
+ }
+}
+
+/// Returns the fractional part of a number.
+///
+/// If the number is an integer, returns `0`.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.fract(3) == 0)
+/// #calc.fract(-3.1)
+/// ```
+///
+/// Display: Fractional
+/// Category: calculate
+#[func]
+pub fn fract(
+ /// The number to truncate.
+ value: Num,
+) -> Num {
+ match value {
+ Num::Int(_) => Num::Int(0),
+ Num::Float(n) => Num::Float(n.fract()),
+ }
+}
+
+/// Rounds a number to the nearest integer.
+///
+/// Optionally, a number of decimal places can be specified.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.round(3.14) == 3)
+/// #assert(calc.round(3.5) == 4)
+/// #calc.round(3.1415, digits: 2)
+/// ```
+///
+/// Display: Round
+/// Category: calculate
+#[func]
+pub fn round(
+ /// The number to round.
+ value: Num,
+ /// The number of decimal places.
+ #[named]
+ #[default(0)]
+ digits: i64,
+) -> Num {
+ match value {
+ Num::Int(n) if digits == 0 => Num::Int(n),
+ _ => {
+ let n = value.float();
+ let factor = 10.0_f64.powi(digits as i32);
+ Num::Float((n * factor).round() / factor)
+ }
+ }
+}
+
+/// Clamps a number between a minimum and maximum value.
+///
+/// ## Example { #example }
+/// ```example
+/// #assert(calc.clamp(5, 0, 10) == 5)
+/// #assert(calc.clamp(5, 6, 10) == 6)
+/// #calc.clamp(5, 0, 4)
+/// ```
+///
+/// Display: Clamp
+/// Category: calculate
+#[func]
+pub fn clamp(
+ /// The number to clamp.
+ value: Num,
+ /// The inclusive minimum value.
+ min: Num,
+ /// The inclusive maximum value.
+ max: Spanned<Num>,
+) -> SourceResult<Num> {
+ if max.v.float() < min.float() {
+ bail!(max.span, "max must be greater than or equal to min")
+ }
+ Ok(value.apply3(min, max.v, i64::clamp, f64::clamp))
+}
+
+/// Determines the minimum of a sequence of values.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.min(1, -3, -5, 20, 3, 6) \
+/// #calc.min("typst", "in", "beta")
+/// ```
+///
+/// Display: Minimum
+/// Category: calculate
+#[func]
+pub fn min(
+ /// The sequence of values from which to extract the minimum.
+ /// Must not be empty.
+ #[variadic]
+ values: Vec<Spanned<Value>>,
+ /// The callsite span.
+ span: Span,
+) -> SourceResult<Value> {
+ minmax(span, values, Ordering::Less)
+}
+
+/// Determines the maximum of a sequence of values.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.max(1, -3, -5, 20, 3, 6) \
+/// #calc.max("typst", "in", "beta")
+/// ```
+///
+/// Display: Maximum
+/// Category: calculate
+#[func]
+pub fn max(
+ /// The sequence of values from which to extract the maximum.
+ /// Must not be empty.
+ #[variadic]
+ values: Vec<Spanned<Value>>,
+ /// The callsite span.
+ span: Span,
+) -> SourceResult<Value> {
+ minmax(span, values, Ordering::Greater)
+}
+
+/// Find the minimum or maximum of a sequence of values.
+fn minmax(
+ span: Span,
+ values: Vec<Spanned<Value>>,
+ goal: Ordering,
+) -> SourceResult<Value> {
+ let mut iter = values.into_iter();
+ let Some(Spanned { v: mut extremum, ..}) = iter.next() else {
+ bail!(span, "expected at least one value");
+ };
+
+ for Spanned { v, span } in iter {
+ let ordering = typst::eval::ops::compare(&v, &extremum).at(span)?;
+ if ordering == goal {
+ extremum = v;
+ }
+ }
+
+ Ok(extremum)
+}
+
+/// Determines whether an integer is even.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.even(4) \
+/// #calc.even(5) \
+/// #range(10).filter(calc.even)
+/// ```
+///
+/// Display: Even
+/// Category: calculate
+#[func]
+pub fn even(
+ /// The number to check for evenness.
+ value: i64,
+) -> bool {
+ value % 2 == 0
+}
+
+/// Determines whether an integer is odd.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.odd(4) \
+/// #calc.odd(5) \
+/// #range(10).filter(calc.odd)
+/// ```
+///
+/// Display: Odd
+/// Category: calculate
+#[func]
+pub fn odd(
+ /// The number to check for oddness.
+ value: i64,
+) -> bool {
+ value % 2 != 0
+}
+
+/// Calculates the remainder of two numbers.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.rem(20, 6) \
+/// #calc.rem(1.75, 0.5)
+/// ```
+///
+/// Display: Remainder
+/// Category: calculate
+#[func]
+pub fn rem(
+ /// The dividend of the remainder.
+ dividend: Num,
+ /// The divisor of the remainder.
+ divisor: Spanned<Num>,
+) -> SourceResult<Num> {
+ if divisor.v.float() == 0.0 {
+ bail!(divisor.span, "divisor must not be zero");
+ }
+ Ok(dividend.apply2(divisor.v, Rem::rem, Rem::rem))
+}
+
+/// Calculates the quotient of two numbers.
+///
+/// ## Example { #example }
+/// ```example
+/// #calc.quo(14, 5) \
+/// #calc.quo(3.46, 0.5)
+/// ```
+///
+/// Display: Quotient
+/// Category: calculate
+#[func]
+pub fn quo(
+ /// The dividend of the quotient.
+ dividend: Num,
+ /// The divisor of the quotient.
+ divisor: Spanned<Num>,
+) -> SourceResult<i64> {
+ if divisor.v.float() == 0.0 {
+ bail!(divisor.span, "divisor must not be zero");
+ }
+
+ Ok(floor(dividend.apply2(divisor.v, Div::div, Div::div)))
+}
+
+/// A value which can be passed to functions that work with integers and floats.
+#[derive(Debug, Copy, Clone)]
+pub enum Num {
+ Int(i64),
+ Float(f64),
+}
+
+impl Num {
+ pub fn apply2(
+ self,
+ other: Self,
+ int: impl FnOnce(i64, i64) -> i64,
+ float: impl FnOnce(f64, f64) -> f64,
+ ) -> Num {
+ match (self, other) {
+ (Self::Int(a), Self::Int(b)) => Num::Int(int(a, b)),
+ (a, b) => Num::Float(float(a.float(), b.float())),
+ }
+ }
+
+ pub fn apply3(
+ self,
+ other: Self,
+ third: Self,
+ int: impl FnOnce(i64, i64, i64) -> i64,
+ float: impl FnOnce(f64, f64, f64) -> f64,
+ ) -> Num {
+ match (self, other, third) {
+ (Self::Int(a), Self::Int(b), Self::Int(c)) => Num::Int(int(a, b, c)),
+ (a, b, c) => Num::Float(float(a.float(), b.float(), c.float())),
+ }
+ }
+
+ pub fn float(self) -> f64 {
+ match self {
+ Self::Int(v) => v as f64,
+ Self::Float(v) => v,
+ }
+ }
+}
+
+cast! {
+ Num,
+ self => match self {
+ Self::Int(v) => v.into_value(),
+ Self::Float(v) => v.into_value(),
+ },
+ v: i64 => Self::Int(v),
+ v: f64 => Self::Float(v),
+}
+
+/// A value that can be passed to a trigonometric function.
+pub enum AngleLike {
+ Int(i64),
+ Float(f64),
+ Angle(Angle),
+}
+
+cast! {
+ AngleLike,
+ v: i64 => Self::Int(v),
+ v: f64 => Self::Float(v),
+ v: Angle => Self::Angle(v),
+}