Add quo, trunc and fract calculation methods and rename mod to rem (#890)

This commit is contained in:
HarmoGlace 2023-04-20 16:09:41 +02:00 committed by GitHub
parent c117e2dc27
commit 42b93b7b53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 29 deletions

View File

@ -2,7 +2,7 @@
use std::cmp; use std::cmp;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::Rem; use std::ops::{Div, Rem};
use typst::eval::{Module, Scope}; use typst::eval::{Module, Scope};
@ -32,13 +32,16 @@ pub fn module() -> Module {
scope.define("lcm", lcm); scope.define("lcm", lcm);
scope.define("floor", floor); scope.define("floor", floor);
scope.define("ceil", ceil); scope.define("ceil", ceil);
scope.define("trunc", trunc);
scope.define("fract", fract);
scope.define("round", round); scope.define("round", round);
scope.define("clamp", clamp); scope.define("clamp", clamp);
scope.define("min", min); scope.define("min", min);
scope.define("max", max); scope.define("max", max);
scope.define("even", even); scope.define("even", even);
scope.define("odd", odd); scope.define("odd", odd);
scope.define("mod", mod_); scope.define("rem", rem);
scope.define("quo", quo);
scope.define("inf", Value::Float(f64::INFINITY)); scope.define("inf", Value::Float(f64::INFINITY));
scope.define("nan", Value::Float(f64::NAN)); scope.define("nan", Value::Float(f64::NAN));
scope.define("pi", Value::Float(std::f64::consts::PI)); scope.define("pi", Value::Float(std::f64::consts::PI));
@ -664,6 +667,55 @@ pub fn ceil(
} }
} }
/// Returns the integer part of a number.
///
/// If the number is already an integer, it is returned unchanged.
///
/// ## Example
/// ```example
/// #assert(calc.trunc(3) == 3)
/// #assert(calc.trunc(-3.7) == -3)
/// #assert(calc.trunc(15.9) == 15)
/// ```
///
/// Display: Truncate
/// Category: calculate
/// Returns: integer
#[func]
pub fn trunc(
/// The number to truncate.
value: Num,
) -> Value {
Value::Int(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, it returns `0`.
///
/// ## Example
/// ```example
/// #assert(calc.fract(3) == 0)
/// #calc.fract(-3.1)
/// ```
///
/// Display: Fractional
/// Category: calculate
/// Returns: integer or float
#[func]
pub fn fract(
/// The number to truncate.
value: Num,
) -> Value {
match value {
Num::Int(_) => Value::Int(0),
Num::Float(n) => Value::Float(n.fract()),
}
}
/// Round a number to the nearest integer. /// Round a number to the nearest integer.
/// ///
/// Optionally, a number of decimal places can be specified. /// Optionally, a number of decimal places can be specified.
@ -721,7 +773,7 @@ pub fn clamp(
if max.v.float() < min.float() { if max.v.float() < min.float() {
bail!(max.span, "max must be greater than or equal to min") bail!(max.span, "max must be greater than or equal to min")
} }
value.apply3(min, max.v, i64::clamp, f64::clamp) value.apply3(min, max.v, i64::clamp, f64::clamp).value()
} }
/// Determine the minimum of a sequence of values. /// Determine the minimum of a sequence of values.
@ -836,28 +888,56 @@ pub fn odd(
Value::Bool(value % 2 != 0) Value::Bool(value % 2 != 0)
} }
/// Calculate the modulus of two numbers. /// Calculate the remainder of two numbers.
/// ///
/// ## Example /// ## Example
/// ```example /// ```example
/// #calc.mod(20, 6) \ /// #calc.rem(20, 6) \
/// #calc.mod(1.75, 0.5) /// #calc.rem(1.75, 0.5)
/// ``` /// ```
/// ///
/// Display: Modulus /// Display: Remainder
/// Category: calculate /// Category: calculate
/// Returns: integer or float /// Returns: integer or float
#[func] #[func]
pub fn mod_( pub fn rem(
/// The dividend of the modulus. /// The dividend of the remainder.
dividend: Num, dividend: Num,
/// The divisor of the modulus. /// The divisor of the remainder.
divisor: Spanned<Num>, divisor: Spanned<Num>,
) -> Value { ) -> Value {
if divisor.v.float() == 0.0 { if divisor.v.float() == 0.0 {
bail!(divisor.span, "divisor must not be zero"); bail!(divisor.span, "divisor must not be zero");
} }
dividend.apply2(divisor.v, Rem::rem, Rem::rem) dividend.apply2(divisor.v, Rem::rem, Rem::rem).value()
}
/// Calculate the quotient of two numbers.
///
/// ## Example
/// ```example
/// #calc.quo(14, 5) \
/// #calc.quo(3.46, 0.5)
/// ```
///
/// Display: Quotient
/// Category: calculate
/// Returns: integer or float
#[func]
pub fn quo(
/// The dividend of the quotient.
dividend: Num,
/// The divisor of the quotient.
divisor: Spanned<Num>,
) -> Value {
if divisor.v.float() == 0.0 {
bail!(divisor.span, "divisor must not be zero");
}
Value::Int(match dividend.apply2(divisor.v, Div::div, Div::div) {
Num::Int(i) => i,
Num::Float(f) => f.floor() as i64, // Note: the result should be an integer but floats doesn't have the same precision as i64.
})
} }
/// A value which can be passed to functions that work with integers and floats. /// A value which can be passed to functions that work with integers and floats.
@ -873,10 +953,10 @@ impl Num {
other: Self, other: Self,
int: impl FnOnce(i64, i64) -> i64, int: impl FnOnce(i64, i64) -> i64,
float: impl FnOnce(f64, f64) -> f64, float: impl FnOnce(f64, f64) -> f64,
) -> Value { ) -> Num {
match (self, other) { match (self, other) {
(Self::Int(a), Self::Int(b)) => Value::Int(int(a, b)), (Self::Int(a), Self::Int(b)) => Num::Int(int(a, b)),
(a, b) => Value::Float(float(a.float(), b.float())), (a, b) => Num::Float(float(a.float(), b.float())),
} }
} }
@ -886,10 +966,10 @@ impl Num {
third: Self, third: Self,
int: impl FnOnce(i64, i64, i64) -> i64, int: impl FnOnce(i64, i64, i64) -> i64,
float: impl FnOnce(f64, f64, f64) -> f64, float: impl FnOnce(f64, f64, f64) -> f64,
) -> Value { ) -> Num {
match (self, other, third) { match (self, other, third) {
(Self::Int(a), Self::Int(b), Self::Int(c)) => Value::Int(int(a, b, c)), (Self::Int(a), Self::Int(b), Self::Int(c)) => Num::Int(int(a, b, c)),
(a, b, c) => Value::Float(float(a.float(), b.float(), c.float())), (a, b, c) => Num::Float(float(a.float(), b.float(), c.float())),
} }
} }

View File

@ -41,7 +41,7 @@
#while x < 8 { #while x < 8 {
i += 1 i += 1
if calc.mod(i, 3) == 0 { if calc.rem(i, 3) == 0 {
continue continue
} }
x += i x += i
@ -55,7 +55,7 @@
#let x = for i in range(5) { #let x = for i in range(5) {
"a" "a"
if calc.mod(i, 3) == 0 { if calc.rem(i, 3) == 0 {
"_" "_"
continue continue
} }

View File

@ -103,7 +103,7 @@
let caps = match.captures let caps = match.captures
time += 60 * int(caps.at(0)) + int(caps.at(1)) time += 60 * int(caps.at(0)) + int(caps.at(1))
} }
str(int(time / 60)) + ":" + str(calc.mod(time, 60)) str(int(time / 60)) + ":" + str(calc.rem(time, 60))
} }
#test(timesum(""), "0:0") #test(timesum(""), "0:0")

View File

@ -55,20 +55,36 @@
#test(calc.even(-11), false) #test(calc.even(-11), false)
--- ---
// Test the `mod` function. // Test the `rem` function.
#test(calc.mod(1, 1), 0) #test(calc.rem(1, 1), 0)
#test(calc.mod(5, 3), 2) #test(calc.rem(5, 3), 2)
#test(calc.mod(5, -3), 2) #test(calc.rem(5, -3), 2)
#test(calc.mod(22.5, 10), 2.5) #test(calc.rem(22.5, 10), 2.5)
#test(calc.mod(9, 4.5), 0) #test(calc.rem(9, 4.5), 0)
--- ---
// Error: 14-15 divisor must not be zero // Error: 14-15 divisor must not be zero
#calc.mod(5, 0) #calc.rem(5, 0)
--- ---
// Error: 16-19 divisor must not be zero // Error: 16-19 divisor must not be zero
#calc.mod(3.0, 0.0) #calc.rem(3.0, 0.0)
---
// Test the `quo` function.
#test(calc.quo(1, 1), 1)
#test(calc.quo(5, 3), 1)
#test(calc.quo(5, -3), -1)
#test(calc.quo(22.5, 10), 2)
#test(calc.quo(9, 4.5), 2)
---
// Error: 14-15 divisor must not be zero
#calc.quo(5, 0)
---
// Error: 16-19 divisor must not be zero
#calc.quo(3.0, 0.0)
--- ---
// Test the `min` and `max` functions. // Test the `min` and `max` functions.

View File

@ -22,7 +22,7 @@
spacing: 0.65em - 3pt, spacing: 0.65em - 3pt,
tight: false, tight: false,
numbering: n => text( numbering: n => text(
fill: (red, green, blue).at(calc.mod(n, 3)), fill: (red, green, blue).at(calc.rem(n, 3)),
numbering("A", n), numbering("A", n),
), ),
[Red], [Green], [Blue], [Red], [Red], [Green], [Blue], [Red],