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::Ordering;
use std::ops::Rem;
use std::ops::{Div, Rem};
use typst::eval::{Module, Scope};
@ -32,13 +32,16 @@ pub fn module() -> Module {
scope.define("lcm", lcm);
scope.define("floor", floor);
scope.define("ceil", ceil);
scope.define("trunc", trunc);
scope.define("fract", fract);
scope.define("round", round);
scope.define("clamp", clamp);
scope.define("min", min);
scope.define("max", max);
scope.define("even", even);
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("nan", Value::Float(f64::NAN));
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.
///
/// Optionally, a number of decimal places can be specified.
@ -721,7 +773,7 @@ pub fn clamp(
if max.v.float() < min.float() {
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.
@ -836,28 +888,56 @@ pub fn odd(
Value::Bool(value % 2 != 0)
}
/// Calculate the modulus of two numbers.
/// Calculate the remainder of two numbers.
///
/// ## Example
/// ```example
/// #calc.mod(20, 6) \
/// #calc.mod(1.75, 0.5)
/// #calc.rem(20, 6) \
/// #calc.rem(1.75, 0.5)
/// ```
///
/// Display: Modulus
/// Display: Remainder
/// Category: calculate
/// Returns: integer or float
#[func]
pub fn mod_(
/// The dividend of the modulus.
pub fn rem(
/// The dividend of the remainder.
dividend: Num,
/// The divisor of the modulus.
/// The divisor of the remainder.
divisor: Spanned<Num>,
) -> Value {
if divisor.v.float() == 0.0 {
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.
@ -873,10 +953,10 @@ impl Num {
other: Self,
int: impl FnOnce(i64, i64) -> i64,
float: impl FnOnce(f64, f64) -> f64,
) -> Value {
) -> Num {
match (self, other) {
(Self::Int(a), Self::Int(b)) => Value::Int(int(a, b)),
(a, b) => Value::Float(float(a.float(), b.float())),
(Self::Int(a), Self::Int(b)) => Num::Int(int(a, b)),
(a, b) => Num::Float(float(a.float(), b.float())),
}
}
@ -886,10 +966,10 @@ impl Num {
third: Self,
int: impl FnOnce(i64, i64, i64) -> i64,
float: impl FnOnce(f64, f64, f64) -> f64,
) -> Value {
) -> Num {
match (self, other, third) {
(Self::Int(a), Self::Int(b), Self::Int(c)) => Value::Int(int(a, b, c)),
(a, b, c) => Value::Float(float(a.float(), b.float(), c.float())),
(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())),
}
}

View File

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

View File

@ -103,7 +103,7 @@
let caps = match.captures
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")

View File

@ -55,20 +55,36 @@
#test(calc.even(-11), false)
---
// Test the `mod` function.
#test(calc.mod(1, 1), 0)
#test(calc.mod(5, 3), 2)
#test(calc.mod(5, -3), 2)
#test(calc.mod(22.5, 10), 2.5)
#test(calc.mod(9, 4.5), 0)
// Test the `rem` function.
#test(calc.rem(1, 1), 0)
#test(calc.rem(5, 3), 2)
#test(calc.rem(5, -3), 2)
#test(calc.rem(22.5, 10), 2.5)
#test(calc.rem(9, 4.5), 0)
---
// Error: 14-15 divisor must not be zero
#calc.mod(5, 0)
#calc.rem(5, 0)
---
// 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.

View File

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