2025-07-01 11:04:31 +00:00

1235 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Calculations and processing of numeric values.
use std::cmp;
use std::cmp::Ordering;
use az::SaturatingAs;
use typst_syntax::{Span, Spanned};
use typst_utils::{round_int_with_precision, round_with_precision};
use crate::diag::{bail, At, HintedString, SourceResult, StrResult};
use crate::foundations::{cast, func, ops, Decimal, IntoValue, Module, Scope, Value};
use crate::layout::{Angle, Fr, Length, Ratio};
/// A module with calculation definitions.
pub fn module() -> Module {
let mut scope = Scope::new();
scope.define_func::<abs>();
scope.define_func::<pow>();
scope.define_func::<exp>();
scope.define_func::<sqrt>();
scope.define_func::<root>();
scope.define_func::<sin>();
scope.define_func::<cos>();
scope.define_func::<tan>();
scope.define_func::<asin>();
scope.define_func::<acos>();
scope.define_func::<atan>();
scope.define_func::<atan2>();
scope.define_func::<sinh>();
scope.define_func::<cosh>();
scope.define_func::<tanh>();
scope.define_func::<log>();
scope.define_func::<ln>();
scope.define_func::<fact>();
scope.define_func::<perm>();
scope.define_func::<binom>();
scope.define_func::<gcd>();
scope.define_func::<lcm>();
scope.define_func::<floor>();
scope.define_func::<ceil>();
scope.define_func::<trunc>();
scope.define_func::<fract>();
scope.define_func::<round>();
scope.define_func::<clamp>();
scope.define_func::<min>();
scope.define_func::<max>();
scope.define_func::<even>();
scope.define_func::<odd>();
scope.define_func::<rem>();
scope.define_func::<div_euclid>();
scope.define_func::<rem_euclid>();
scope.define_func::<quo>();
scope.define_func::<norm>();
scope.define("inf", f64::INFINITY);
scope.define("pi", std::f64::consts::PI);
scope.define("tau", std::f64::consts::TAU);
scope.define("e", std::f64::consts::E);
Module::new("calc", scope)
}
/// Calculates the absolute value of a numeric value.
///
/// ```example
/// #calc.abs(-5) \
/// #calc.abs(5pt - 2cm) \
/// #calc.abs(2fr) \
/// #calc.abs(decimal("-342.440"))
/// ```
#[func(title = "Absolute")]
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())),
v: Decimal => Self(Value::Decimal(v.abs()))
}
/// Raises a value to some exponent.
///
/// ```example
/// #calc.pow(2, 3) \
/// #calc.pow(decimal("2.5"), 2)
/// ```
#[func(title = "Power")]
pub fn pow(
span: Span,
/// The base of the power.
///
/// If this is a [`decimal`], the exponent can only be an [integer]($int).
base: DecNum,
/// The exponent of the power.
exponent: Spanned<Num>,
) -> SourceResult<DecNum> {
match exponent.v {
_ if exponent.v.float() == 0.0 && base.is_zero() => {
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")
}
_ => {}
};
match (base, exponent.v) {
(DecNum::Int(a), Num::Int(b)) if b >= 0 => a
.checked_pow(b as u32)
.map(DecNum::Int)
.ok_or_else(too_large)
.at(span),
(DecNum::Decimal(a), Num::Int(b)) => {
a.checked_powi(b).map(DecNum::Decimal).ok_or_else(too_large).at(span)
}
(a, b) => {
let Some(a) = a.float() else {
return Err(cant_apply_to_decimal_and_float()).at(span);
};
let result = if a == std::f64::consts::E {
b.float().exp()
} else if a == 2.0 {
b.float().exp2()
} else if let Num::Int(b) = b {
a.powi(b as i32)
} else {
a.powf(b.float())
};
if result.is_nan() {
bail!(span, "the result is not a real number")
}
Ok(DecNum::Float(result))
}
}
}
/// Raises a value to some exponent of e.
///
/// ```example
/// #calc.exp(1)
/// ```
#[func(title = "Exponential")]
pub fn exp(
span: Span,
/// The exponent of the power.
exponent: Spanned<Num>,
) -> 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)
}
/// Calculates the square root of a number.
///
/// ```example
/// #calc.sqrt(16) \
/// #calc.sqrt(2.5)
/// ```
#[func(title = "Square Root")]
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 real nth root of a number.
///
/// If the number is negative, then n must be odd.
///
/// ```example
/// #calc.root(16.0, 4) \
/// #calc.root(27.0, 3)
/// ```
#[func]
pub fn root(
/// The expression to take the root of.
radicand: f64,
/// Which root of the radicand to take.
index: Spanned<i64>,
) -> SourceResult<f64> {
if index.v == 0 {
bail!(index.span, "cannot take the 0th root of a number");
} else if radicand < 0.0 {
if index.v % 2 == 0 {
bail!(
index.span,
"negative numbers do not have a real nth root when n is even"
);
} else {
Ok(-(-radicand).powf(1.0 / index.v as f64))
}
} else {
Ok(radicand.powf(1.0 / index.v as f64))
}
}
/// Calculates the sine of an angle.
///
/// When called with an integer or a float, they will be interpreted as
/// radians.
///
/// ```example
/// #calc.sin(1.5) \
/// #calc.sin(90deg)
/// ```
#[func(title = "Sine")]
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
/// #calc.cos(1.5) \
/// #calc.cos(90deg)
/// ```
#[func(title = "Cosine")]
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
/// #calc.tan(1.5) \
/// #calc.tan(90deg)
/// ```
#[func(title = "Tangent")]
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
/// #calc.asin(0) \
/// #calc.asin(1)
/// ```
#[func(title = "Arcsine")]
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
/// #calc.acos(0) \
/// #calc.acos(1)
/// ```
#[func(title = "Arccosine")]
pub fn acos(
/// The number whose arccosine 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
/// #calc.atan(0) \
/// #calc.atan(1)
/// ```
#[func(title = "Arctangent")]
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
/// #calc.atan2(1, 1) \
/// #calc.atan2(-2, -3)
/// ```
#[func(title = "Four-quadrant Arctangent")]
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 a hyperbolic angle.
///
/// ```example
/// #calc.sinh(0) \
/// #calc.sinh(1.5)
/// ```
#[func(title = "Hyperbolic Sine")]
pub fn sinh(
/// The hyperbolic angle whose hyperbolic sine to calculate.
value: f64,
) -> f64 {
value.sinh()
}
/// Calculates the hyperbolic cosine of a hyperbolic angle.
///
/// ```example
/// #calc.cosh(0) \
/// #calc.cosh(1.5)
/// ```
#[func(title = "Hyperbolic Cosine")]
pub fn cosh(
/// The hyperbolic angle whose hyperbolic cosine to calculate.
value: f64,
) -> f64 {
value.cosh()
}
/// Calculates the hyperbolic tangent of a hyperbolic angle.
///
/// ```example
/// #calc.tanh(0) \
/// #calc.tanh(1.5)
/// ```
#[func(title = "Hyperbolic Tangent")]
pub fn tanh(
/// The hyperbolic angle whose hyperbolic tangent to calculate.
value: f64,
) -> f64 {
value.tanh()
}
/// Calculates the logarithm of a number.
///
/// If the base is not specified, the logarithm is calculated in base 10.
///
/// ```example
/// #calc.log(100)
/// ```
#[func(title = "Logarithm")]
pub fn log(
span: Span,
/// 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>,
) -> 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
/// #calc.ln(calc.e)
/// ```
#[func(title = "Natural Logarithm")]
pub fn ln(
span: Span,
/// The number whose logarithm to calculate. Must be strictly positive.
value: Spanned<Num>,
) -> 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
/// #calc.fact(5)
/// ```
#[func(title = "Factorial")]
pub fn fact(
/// The number whose factorial to calculate. Must be non-negative.
number: u64,
) -> StrResult<i64> {
Ok(fact_impl(1, number).ok_or_else(too_large)?)
}
/// Calculates a permutation.
///
/// Returns the `k`-permutation of `n`, or the number of ways to choose `k`
/// items from a set of `n` with regard to order.
///
/// ```example
/// $ "perm"(n, k) &= n!/((n - k)!) \
/// "perm"(5, 3) &= #calc.perm(5, 3) $
/// ```
#[func(title = "Permutation")]
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_else(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.
///
/// Returns the `k`-combination of `n`, or the number of ways to choose `k`
/// items from a set of `n` without regard to order.
///
/// ```example
/// #calc.binom(10, 5)
/// ```
#[func(title = "Binomial")]
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_else(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
/// #calc.gcd(7, 42)
/// ```
#[func(title = "Greatest Common Divisor")]
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
/// #calc.lcm(96, 13)
/// ```
#[func(title = "Least Common Multiple")]
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_else(too_large)?)
}
/// Rounds a number down to the nearest integer.
///
/// If the number is already an integer, it is returned unchanged.
///
/// Note that this function will always return an [integer]($int), and will
/// error if the resulting [`float`] or [`decimal`] is larger than the maximum
/// 64-bit signed integer or smaller than the minimum for that type.
///
/// ```example
/// #calc.floor(500.1)
/// #assert(calc.floor(3) == 3)
/// #assert(calc.floor(3.14) == 3)
/// #assert(calc.floor(decimal("-3.14")) == -4)
/// ```
#[func]
pub fn floor(
/// The number to round down.
value: DecNum,
) -> StrResult<i64> {
match value {
DecNum::Int(n) => Ok(n),
DecNum::Float(n) => Ok(crate::foundations::convert_float_to_int(n.floor())
.map_err(|_| too_large())?),
DecNum::Decimal(n) => Ok(i64::try_from(n.floor()).map_err(|_| too_large())?),
}
}
/// Rounds a number up to the nearest integer.
///
/// If the number is already an integer, it is returned unchanged.
///
/// Note that this function will always return an [integer]($int), and will
/// error if the resulting [`float`] or [`decimal`] is larger than the maximum
/// 64-bit signed integer or smaller than the minimum for that type.
///
/// ```example
/// #calc.ceil(500.1)
/// #assert(calc.ceil(3) == 3)
/// #assert(calc.ceil(3.14) == 4)
/// #assert(calc.ceil(decimal("-3.14")) == -3)
/// ```
#[func]
pub fn ceil(
/// The number to round up.
value: DecNum,
) -> StrResult<i64> {
match value {
DecNum::Int(n) => Ok(n),
DecNum::Float(n) => Ok(crate::foundations::convert_float_to_int(n.ceil())
.map_err(|_| too_large())?),
DecNum::Decimal(n) => Ok(i64::try_from(n.ceil()).map_err(|_| too_large())?),
}
}
/// Returns the integer part of a number.
///
/// If the number is already an integer, it is returned unchanged.
///
/// Note that this function will always return an [integer]($int), and will
/// error if the resulting [`float`] or [`decimal`] is larger than the maximum
/// 64-bit signed integer or smaller than the minimum for that type.
///
/// ```example
/// #calc.trunc(15.9)
/// #assert(calc.trunc(3) == 3)
/// #assert(calc.trunc(-3.7) == -3)
/// #assert(calc.trunc(decimal("8493.12949582390")) == 8493)
/// ```
#[func(title = "Truncate")]
pub fn trunc(
/// The number to truncate.
value: DecNum,
) -> StrResult<i64> {
match value {
DecNum::Int(n) => Ok(n),
DecNum::Float(n) => Ok(crate::foundations::convert_float_to_int(n.trunc())
.map_err(|_| too_large())?),
DecNum::Decimal(n) => Ok(i64::try_from(n.trunc()).map_err(|_| too_large())?),
}
}
/// Returns the fractional part of a number.
///
/// If the number is an integer, returns `0`.
///
/// ```example
/// #calc.fract(-3.1)
/// #assert(calc.fract(3) == 0)
/// #assert(calc.fract(decimal("234.23949211")) == decimal("0.23949211"))
/// ```
#[func(title = "Fractional")]
pub fn fract(
/// The number to truncate.
value: DecNum,
) -> DecNum {
match value {
DecNum::Int(_) => DecNum::Int(0),
DecNum::Float(n) => DecNum::Float(n.fract()),
DecNum::Decimal(n) => DecNum::Decimal(n.fract()),
}
}
/// Rounds a number to the nearest integer.
///
/// Half-integers are rounded away from zero.
///
/// Optionally, a number of decimal places can be specified. If negative, its
/// absolute value will indicate the amount of significant integer digits to
/// remove before the decimal point.
///
/// Note that this function will return the same type as the operand. That is,
/// applying `round` to a [`float`] will return a `float`, and to a [`decimal`],
/// another `decimal`. You may explicitly convert the output of this function to
/// an integer with [`int`], but note that such a conversion will error if the
/// `float` or `decimal` is larger than the maximum 64-bit signed integer or
/// smaller than the minimum integer.
///
/// In addition, this function can error if there is an attempt to round beyond
/// the maximum or minimum integer or `decimal`. If the number is a `float`,
/// such an attempt will cause `{float.inf}` or `{-float.inf}` to be returned
/// for maximum and minimum respectively.
///
/// ```example
/// #calc.round(3.1415, digits: 2)
/// #assert(calc.round(3) == 3)
/// #assert(calc.round(3.14) == 3)
/// #assert(calc.round(3.5) == 4.0)
/// #assert(calc.round(3333.45, digits: -2) == 3300.0)
/// #assert(calc.round(-48953.45, digits: -3) == -49000.0)
/// #assert(calc.round(3333, digits: -2) == 3300)
/// #assert(calc.round(-48953, digits: -3) == -49000)
/// #assert(calc.round(decimal("-6.5")) == decimal("-7"))
/// #assert(calc.round(decimal("7.123456789"), digits: 6) == decimal("7.123457"))
/// #assert(calc.round(decimal("3333.45"), digits: -2) == decimal("3300"))
/// #assert(calc.round(decimal("-48953.45"), digits: -3) == decimal("-49000"))
/// ```
#[func]
pub fn round(
/// The number to round.
value: DecNum,
/// If positive, the number of decimal places.
///
/// If negative, the number of significant integer digits that should be
/// removed before the decimal point.
#[named]
#[default(0)]
digits: i64,
) -> StrResult<DecNum> {
match value {
DecNum::Int(n) => Ok(DecNum::Int(
round_int_with_precision(n, digits.saturating_as::<i16>())
.ok_or_else(too_large)?,
)),
DecNum::Float(n) => {
Ok(DecNum::Float(round_with_precision(n, digits.saturating_as::<i16>())))
}
DecNum::Decimal(n) => Ok(DecNum::Decimal(
n.round(digits.saturating_as::<i32>()).ok_or_else(too_large)?,
)),
}
}
/// Clamps a number between a minimum and maximum value.
///
/// ```example
/// #calc.clamp(5, 0, 4)
/// #assert(calc.clamp(5, 0, 10) == 5)
/// #assert(calc.clamp(5, 6, 10) == 6)
/// #assert(calc.clamp(decimal("5.45"), 2, decimal("45.9")) == decimal("5.45"))
/// #assert(calc.clamp(decimal("5.45"), decimal("6.75"), 12) == decimal("6.75"))
/// ```
#[func]
pub fn clamp(
span: Span,
/// The number to clamp.
value: DecNum,
/// The inclusive minimum value.
min: DecNum,
/// The inclusive maximum value.
max: Spanned<DecNum>,
) -> SourceResult<DecNum> {
// Ignore if there are incompatible types (decimal and float) since that
// will cause `apply3` below to error before calling clamp, avoiding a
// panic.
if min
.apply2(max.v, |min, max| max < min, |min, max| max < min, |min, max| max < min)
.unwrap_or(false)
{
bail!(max.span, "max must be greater than or equal to min")
}
value
.apply3(min, max.v, i64::clamp, f64::clamp, Decimal::clamp)
.ok_or_else(cant_apply_to_decimal_and_float)
.at(span)
}
/// Determines the minimum of a sequence of values.
///
/// ```example
/// #calc.min(1, -3, -5, 20, 3, 6) \
/// #calc.min("typst", "is", "cool")
/// ```
#[func(title = "Minimum")]
pub fn min(
span: Span,
/// The sequence of values from which to extract the minimum.
/// Must not be empty.
#[variadic]
values: Vec<Spanned<Value>>,
) -> SourceResult<Value> {
minmax(span, values, Ordering::Less)
}
/// Determines the maximum of a sequence of values.
///
/// ```example
/// #calc.max(1, -3, -5, 20, 3, 6) \
/// #calc.max("typst", "is", "cool")
/// ```
#[func(title = "Maximum")]
pub fn max(
span: Span,
/// The sequence of values from which to extract the maximum.
/// Must not be empty.
#[variadic]
values: Vec<Spanned<Value>>,
) -> 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 = ops::compare(&v, &extremum).at(span)?;
if ordering == goal {
extremum = v;
}
}
Ok(extremum)
}
/// Determines whether an integer is even.
///
/// ```example
/// #calc.even(4) \
/// #calc.even(5) \
/// #range(10).filter(calc.even)
/// ```
#[func]
pub fn even(
/// The number to check for evenness.
value: i64,
) -> bool {
value % 2 == 0
}
/// Determines whether an integer is odd.
///
/// ```example
/// #calc.odd(4) \
/// #calc.odd(5) \
/// #range(10).filter(calc.odd)
/// ```
#[func]
pub fn odd(
/// The number to check for oddness.
value: i64,
) -> bool {
value % 2 != 0
}
/// Calculates the remainder of two numbers.
///
/// The value `calc.rem(x, y)` always has the same sign as `x`, and is smaller
/// in magnitude than `y`.
///
/// This can error if given a [`decimal`] input and the dividend is too small in
/// magnitude compared to the divisor.
///
/// ```example
/// #calc.rem(7, 3) \
/// #calc.rem(7, -3) \
/// #calc.rem(-7, 3) \
/// #calc.rem(-7, -3) \
/// #calc.rem(1.75, 0.5)
/// ```
#[func(title = "Remainder")]
pub fn rem(
span: Span,
/// The dividend of the remainder.
dividend: DecNum,
/// The divisor of the remainder.
divisor: Spanned<DecNum>,
) -> SourceResult<DecNum> {
if divisor.v.is_zero() {
bail!(divisor.span, "divisor must not be zero");
}
dividend
.apply2(
divisor.v,
|a, b| Some(DecNum::Int(a % b)),
|a, b| Some(DecNum::Float(a % b)),
|a, b| a.checked_rem(b).map(DecNum::Decimal),
)
.ok_or_else(cant_apply_to_decimal_and_float)
.at(span)?
.ok_or("dividend too small compared to divisor")
.at(span)
}
/// Performs euclidean division of two numbers.
///
/// The result of this computation is that of a division rounded to the integer
/// `{n}` such that the dividend is greater than or equal to `{n}` times the divisor.
///
/// ```example
/// #calc.div-euclid(7, 3) \
/// #calc.div-euclid(7, -3) \
/// #calc.div-euclid(-7, 3) \
/// #calc.div-euclid(-7, -3) \
/// #calc.div-euclid(1.75, 0.5) \
/// #calc.div-euclid(decimal("1.75"), decimal("0.5"))
/// ```
#[func(title = "Euclidean Division")]
pub fn div_euclid(
span: Span,
/// The dividend of the division.
dividend: DecNum,
/// The divisor of the division.
divisor: Spanned<DecNum>,
) -> SourceResult<DecNum> {
if divisor.v.is_zero() {
bail!(divisor.span, "divisor must not be zero");
}
dividend
.apply2(
divisor.v,
|a, b| Some(DecNum::Int(a.div_euclid(b))),
|a, b| Some(DecNum::Float(a.div_euclid(b))),
|a, b| a.checked_div_euclid(b).map(DecNum::Decimal),
)
.ok_or_else(cant_apply_to_decimal_and_float)
.at(span)?
.ok_or_else(too_large)
.at(span)
}
/// This calculates the least nonnegative remainder of a division.
///
/// Warning: Due to a floating point round-off error, the remainder may equal
/// the absolute value of the divisor if the dividend is much smaller in
/// magnitude than the divisor and the dividend is negative. This only applies
/// for floating point inputs.
///
/// In addition, this can error if given a [`decimal`] input and the dividend is
/// too small in magnitude compared to the divisor.
///
/// ```example
/// #calc.rem-euclid(7, 3) \
/// #calc.rem-euclid(7, -3) \
/// #calc.rem-euclid(-7, 3) \
/// #calc.rem-euclid(-7, -3) \
/// #calc.rem-euclid(1.75, 0.5) \
/// #calc.rem-euclid(decimal("1.75"), decimal("0.5"))
/// ```
#[func(title = "Euclidean Remainder", keywords = ["modulo", "modulus"])]
pub fn rem_euclid(
span: Span,
/// The dividend of the remainder.
dividend: DecNum,
/// The divisor of the remainder.
divisor: Spanned<DecNum>,
) -> SourceResult<DecNum> {
if divisor.v.is_zero() {
bail!(divisor.span, "divisor must not be zero");
}
dividend
.apply2(
divisor.v,
|a, b| Some(DecNum::Int(a.rem_euclid(b))),
|a, b| Some(DecNum::Float(a.rem_euclid(b))),
|a, b| a.checked_rem_euclid(b).map(DecNum::Decimal),
)
.ok_or_else(cant_apply_to_decimal_and_float)
.at(span)?
.ok_or("dividend too small compared to divisor")
.at(span)
}
/// Calculates the quotient (floored division) of two numbers.
///
/// Note that this function will always return an [integer]($int), and will
/// error if the resulting [`float`] or [`decimal`] is larger than the maximum
/// 64-bit signed integer or smaller than the minimum for that type.
///
/// ```example
/// $ "quo"(a, b) &= floor(a/b) \
/// "quo"(14, 5) &= #calc.quo(14, 5) \
/// "quo"(3.46, 0.5) &= #calc.quo(3.46, 0.5) $
/// ```
#[func(title = "Quotient")]
pub fn quo(
span: Span,
/// The dividend of the quotient.
dividend: DecNum,
/// The divisor of the quotient.
divisor: Spanned<DecNum>,
) -> SourceResult<i64> {
if divisor.v.is_zero() {
bail!(divisor.span, "divisor must not be zero");
}
let divided = dividend
.apply2(
divisor.v,
|a, b| Some(DecNum::Int(a / b)),
|a, b| Some(DecNum::Float(a / b)),
|a, b| a.checked_div(b).map(DecNum::Decimal),
)
.ok_or_else(cant_apply_to_decimal_and_float)
.at(span)?
.ok_or_else(too_large)
.at(span)?;
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<f64>,
/// The sequence of values from which to calculate the p-norm.
/// Returns `0.0` if empty.
#[variadic]
values: Vec<f64>,
) -> SourceResult<f64> {
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::<f64>().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 {
Int(i64),
Float(f64),
}
impl Num {
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 which can be passed to functions that work with integers, floats,
/// and decimals.
#[derive(Debug, Copy, Clone)]
pub enum DecNum {
Int(i64),
Float(f64),
Decimal(Decimal),
}
impl DecNum {
/// Checks if this number is equivalent to zero.
fn is_zero(self) -> bool {
match self {
Self::Int(i) => i == 0,
Self::Float(f) => f == 0.0,
Self::Decimal(d) => d.is_zero(),
}
}
/// If this `DecNum` holds an integer or float, returns a float.
/// Otherwise, returns `None`.
fn float(self) -> Option<f64> {
match self {
Self::Int(i) => Some(i as f64),
Self::Float(f) => Some(f),
Self::Decimal(_) => None,
}
}
/// If this `DecNum` holds an integer or decimal, returns a decimal.
/// Otherwise, returns `None`.
fn decimal(self) -> Option<Decimal> {
match self {
Self::Int(i) => Some(Decimal::from(i)),
Self::Float(_) => None,
Self::Decimal(d) => Some(d),
}
}
/// Tries to apply a function to two decimal or numeric arguments.
///
/// Fails with `None` if one is a float and the other is a decimal.
fn apply2<T>(
self,
other: Self,
int: impl FnOnce(i64, i64) -> T,
float: impl FnOnce(f64, f64) -> T,
decimal: impl FnOnce(Decimal, Decimal) -> T,
) -> Option<T> {
match (self, other) {
(Self::Int(a), Self::Int(b)) => Some(int(a, b)),
(Self::Decimal(a), Self::Decimal(b)) => Some(decimal(a, b)),
(Self::Decimal(a), Self::Int(b)) => Some(decimal(a, Decimal::from(b))),
(Self::Int(a), Self::Decimal(b)) => Some(decimal(Decimal::from(a), b)),
(a, b) => Some(float(a.float()?, b.float()?)),
}
}
/// Tries to apply a function to three decimal or numeric arguments.
///
/// Fails with `None` if one is a float and the other is a decimal.
fn apply3(
self,
other: Self,
third: Self,
int: impl FnOnce(i64, i64, i64) -> i64,
float: impl FnOnce(f64, f64, f64) -> f64,
decimal: impl FnOnce(Decimal, Decimal, Decimal) -> Decimal,
) -> Option<Self> {
match (self, other, third) {
(Self::Int(a), Self::Int(b), Self::Int(c)) => Some(Self::Int(int(a, b, c))),
(Self::Decimal(a), b, c) => {
Some(Self::Decimal(decimal(a, b.decimal()?, c.decimal()?)))
}
(a, Self::Decimal(b), c) => {
Some(Self::Decimal(decimal(a.decimal()?, b, c.decimal()?)))
}
(a, b, Self::Decimal(c)) => {
Some(Self::Decimal(decimal(a.decimal()?, b.decimal()?, c)))
}
(a, b, c) => Some(Self::Float(float(a.float()?, b.float()?, c.float()?))),
}
}
}
cast! {
DecNum,
self => match self {
Self::Int(v) => v.into_value(),
Self::Float(v) => v.into_value(),
Self::Decimal(v) => v.into_value(),
},
v: i64 => Self::Int(v),
v: f64 => Self::Float(v),
v: Decimal => Self::Decimal(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),
}
/// The error message when the result is too large to be represented.
#[cold]
fn too_large() -> &'static str {
"the result is too large"
}
/// The hinted error message when trying to apply an operation to decimal and
/// float operands.
#[cold]
fn cant_apply_to_decimal_and_float() -> HintedString {
HintedString::new("cannot apply this operation to a decimal and a float".into())
.with_hint(
"if loss of precision is acceptable, explicitly cast the \
decimal to a float with `float(value)`",
)
}