From 42b93b7b534557205a6dc3dfda8fe3cfccfcc458 Mon Sep 17 00:00:00 2001 From: HarmoGlace <23212967+HarmoGlace@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:09:41 +0200 Subject: [PATCH] Add `quo`, `trunc` and `fract` calculation methods and rename `mod` to `rem` (#890) --- library/src/compute/calc.rs | 114 ++++++++++++++++++++++---- tests/typ/compiler/break-continue.typ | 4 +- tests/typ/compiler/string.typ | 2 +- tests/typ/compute/calc.typ | 32 ++++++-- tests/typ/layout/enum-numbering.typ | 2 +- 5 files changed, 125 insertions(+), 29 deletions(-) diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index bba0e3b9c..b6b2442f6 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -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, ) -> 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, +) -> 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())), } } diff --git a/tests/typ/compiler/break-continue.typ b/tests/typ/compiler/break-continue.typ index f6245007e..6f505f80f 100644 --- a/tests/typ/compiler/break-continue.typ +++ b/tests/typ/compiler/break-continue.typ @@ -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 } diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ index 95e204c57..2f7ba9ec7 100644 --- a/tests/typ/compiler/string.typ +++ b/tests/typ/compiler/string.typ @@ -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") diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ index 6af5189b4..c9a37d1b6 100644 --- a/tests/typ/compute/calc.typ +++ b/tests/typ/compute/calc.typ @@ -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. diff --git a/tests/typ/layout/enum-numbering.typ b/tests/typ/layout/enum-numbering.typ index 3eaf352ee..7efe195ff 100644 --- a/tests/typ/layout/enum-numbering.typ +++ b/tests/typ/layout/enum-numbering.typ @@ -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],