mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Better error spans in calc
This commit is contained in:
parent
e11bd2a193
commit
9025ecb2ee
@ -94,28 +94,27 @@ pub fn pow(
|
|||||||
/// The exponent of the power. Must be non-negative.
|
/// The exponent of the power. Must be non-negative.
|
||||||
exponent: Spanned<Num>,
|
exponent: Spanned<Num>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let Spanned { v: exp, span } = exponent;
|
match exponent.v {
|
||||||
match exp {
|
_ if exponent.v.float() == 0.0 && base.float() == 0.0 => {
|
||||||
_ if exp.float() == 0.0 && base.float() == 0.0 => {
|
|
||||||
bail!(args.span, "zero to the power of zero is undefined")
|
bail!(args.span, "zero to the power of zero is undefined")
|
||||||
}
|
}
|
||||||
Num::Int(i) if i32::try_from(i).is_err() => {
|
Num::Int(i) if i32::try_from(i).is_err() => {
|
||||||
bail!(span, "exponent is too large")
|
bail!(exponent.span, "exponent is too large")
|
||||||
}
|
}
|
||||||
Num::Float(f) if !f.is_normal() && f != 0.0 => {
|
Num::Float(f) if !f.is_normal() && f != 0.0 => {
|
||||||
bail!(span, "exponent may not be infinite, subnormal, or NaN")
|
bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN")
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = match (base, exp) {
|
let result = match (base, exponent.v) {
|
||||||
(Num::Int(a), Num::Int(b)) if b >= 0 => Num::Int(a.pow(b as u32)),
|
(Num::Int(a), Num::Int(b)) if b >= 0 => Num::Int(a.pow(b as u32)),
|
||||||
(a, Num::Int(b)) => Num::Float(a.float().powi(b as i32)),
|
(a, Num::Int(b)) => Num::Float(a.float().powi(b as i32)),
|
||||||
(a, b) => Num::Float(a.float().powf(b.float())),
|
(a, b) => Num::Float(a.float().powf(b.float())),
|
||||||
};
|
};
|
||||||
|
|
||||||
if result.float().is_nan() {
|
if result.float().is_nan() {
|
||||||
bail!(span, "the result is not a real number")
|
bail!(args.span, "the result is not a real number")
|
||||||
}
|
}
|
||||||
|
|
||||||
result.value()
|
result.value()
|
||||||
@ -381,28 +380,28 @@ pub fn log(
|
|||||||
value: Spanned<Num>,
|
value: Spanned<Num>,
|
||||||
/// The base of the logarithm. Defaults to `{10}` and may not be zero.
|
/// The base of the logarithm. Defaults to `{10}` and may not be zero.
|
||||||
#[named]
|
#[named]
|
||||||
#[default(10.0)]
|
#[default(Spanned::new(10.0, Span::detached()))]
|
||||||
base: f64,
|
base: Spanned<f64>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let number = value.v.float();
|
let number = value.v.float();
|
||||||
if number <= 0.0 {
|
if number <= 0.0 {
|
||||||
bail!(value.span, "value must be strictly positive")
|
bail!(value.span, "value must be strictly positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !base.is_normal() {
|
if !base.v.is_normal() {
|
||||||
bail!(value.span, "base may not be zero, NaN, infinite, or subnormal")
|
bail!(base.span, "base may not be zero, NaN, infinite, or subnormal")
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if base == 2.0 {
|
let result = if base.v == 2.0 {
|
||||||
number.log2()
|
number.log2()
|
||||||
} else if base == 10.0 {
|
} else if base.v == 10.0 {
|
||||||
number.log10()
|
number.log10()
|
||||||
} else {
|
} else {
|
||||||
number.log(base)
|
number.log(base.v)
|
||||||
};
|
};
|
||||||
|
|
||||||
if result.is_infinite() || result.is_nan() {
|
if result.is_infinite() || result.is_nan() {
|
||||||
bail!(value.span, "the result is not a real number")
|
bail!(args.span, "the result is not a real number")
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::Float(result)
|
Value::Float(result)
|
||||||
@ -420,39 +419,37 @@ pub fn log(
|
|||||||
/// Returns: integer
|
/// Returns: integer
|
||||||
#[func]
|
#[func]
|
||||||
pub fn fact(
|
pub fn fact(
|
||||||
/// The number whose factorial to calculate. Must be positive.
|
/// The number whose factorial to calculate. Must be non-negative.
|
||||||
number: Spanned<u64>,
|
number: u64,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let result = factorial_range(1, number.v).and_then(|r| i64::try_from(r).ok());
|
factorial_range(1, number)
|
||||||
|
.map(Value::Int)
|
||||||
match result {
|
.ok_or("the result is too large")
|
||||||
None => bail!(number.span, "the factorial result is too large"),
|
.at(args.span)?
|
||||||
Some(s) => Value::Int(s),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the product of a range of numbers. Used to calculate permutations.
|
/// Calculates the product of a range of numbers. Used to calculate
|
||||||
/// Returns None if the result is larger than `u64::MAX`
|
/// permutations. Returns None if the result is larger than `i64::MAX`
|
||||||
fn factorial_range(start: u64, end: u64) -> Option<u64> {
|
fn factorial_range(start: u64, end: u64) -> Option<i64> {
|
||||||
// By convention
|
// By convention
|
||||||
if end + 1 < start {
|
if end + 1 < start {
|
||||||
return Some(0);
|
return Some(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut count: u64 = 1;
|
|
||||||
let real_start: u64 = cmp::max(1, start);
|
let real_start: u64 = cmp::max(1, start);
|
||||||
|
let mut count: u64 = 1;
|
||||||
for i in real_start..=end {
|
for i in real_start..=end {
|
||||||
count = count.checked_mul(i)?;
|
count = count.checked_mul(i)?;
|
||||||
}
|
}
|
||||||
Some(count)
|
|
||||||
|
i64::try_from(count).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate a permutation.
|
/// Calculate a permutation.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #calc.perm(10,5)
|
/// #calc.perm(10, 5)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Display: Permutation
|
/// Display: Permutation
|
||||||
@ -460,74 +457,65 @@ fn factorial_range(start: u64, end: u64) -> Option<u64> {
|
|||||||
/// Returns: integer
|
/// Returns: integer
|
||||||
#[func]
|
#[func]
|
||||||
pub fn perm(
|
pub fn perm(
|
||||||
/// The base number. Must be positive.
|
/// The base number. Must be non-negative.
|
||||||
base: Spanned<u64>,
|
base: u64,
|
||||||
/// The number of permutations. Must be positive.
|
/// The number of permutations. Must be non-negative.
|
||||||
numbers: Spanned<u64>,
|
numbers: u64,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let base_parsed = base.v;
|
// By convention.
|
||||||
let numbers_parsed = numbers.v;
|
if base + 1 <= numbers {
|
||||||
|
return Ok(Value::Int(0));
|
||||||
let result = if base_parsed + 1 > numbers_parsed {
|
|
||||||
factorial_range(base_parsed - numbers_parsed + 1, base_parsed)
|
|
||||||
.and_then(|value| i64::try_from(value).ok())
|
|
||||||
} else {
|
|
||||||
// By convention
|
|
||||||
Some(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
None => bail!(base.span, "the permutation result is too large"),
|
|
||||||
Some(s) => Value::Int(s),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factorial_range(base - numbers + 1, base)
|
||||||
|
.map(Value::Int)
|
||||||
|
.ok_or("the result is too large")
|
||||||
|
.at(args.span)?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate a binomial coefficient.
|
/// Calculate a binomial coefficient.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #calc.binom(10,5)
|
/// #calc.binom(10, 5)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Display: Permutation
|
/// Display: Binomial
|
||||||
/// Category: calculate
|
/// Category: calculate
|
||||||
/// Returns: integer
|
/// Returns: integer
|
||||||
#[func]
|
#[func]
|
||||||
pub fn binom(
|
pub fn binom(
|
||||||
/// The upper coefficient. Must be positive
|
/// The upper coefficient. Must be non-negative.
|
||||||
n: Spanned<u64>,
|
n: u64,
|
||||||
/// The lower coefficient. Must be positive.
|
/// The lower coefficient. Must be non-negative.
|
||||||
k: Spanned<u64>,
|
k: u64,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
let result = binomial(n.v, k.v).and_then(|raw| i64::try_from(raw).ok());
|
binomial(n, k)
|
||||||
|
.map(Value::Int)
|
||||||
match result {
|
.ok_or("the result is too large")
|
||||||
None => bail!(n.span, "the binomial result is too large"),
|
.at(args.span)?
|
||||||
Some(r) => Value::Int(r),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates a binomial coefficient, with `n` the upper coefficient and `k` the lower coefficient.
|
/// Calculates a binomial coefficient, with `n` the upper coefficient and `k`
|
||||||
/// Returns `None` if the result is larger than `u64::MAX`
|
/// the lower coefficient. Returns `None` if the result is larger than
|
||||||
fn binomial(n: u64, k: u64) -> Option<u64> {
|
/// `i64::MAX`
|
||||||
|
fn binomial(n: u64, k: u64) -> Option<i64> {
|
||||||
if k > n {
|
if k > n {
|
||||||
return Some(0);
|
return Some(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// By symmetry
|
// By symmetry
|
||||||
let real_k = cmp::min(n - k, k);
|
let real_k = cmp::min(n - k, k);
|
||||||
|
|
||||||
if real_k == 0 {
|
if real_k == 0 {
|
||||||
return Some(1);
|
return Some(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result: u64 = 1;
|
let mut result: u64 = 1;
|
||||||
|
|
||||||
for i in 0..real_k {
|
for i in 0..real_k {
|
||||||
result = result.checked_mul(n - i).and_then(|r| r.checked_div(i + 1))?;
|
result = result.checked_mul(n - i)?.checked_div(i + 1)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(result)
|
i64::try_from(result).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Round a number down to the nearest integer.
|
/// Round a number down to the nearest integer.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
pub use typst_macros::{cast_from_value, cast_to_value, Cast};
|
pub use typst_macros::{cast_from_value, cast_to_value, Cast};
|
||||||
|
|
||||||
use std::num::{NonZeroI64, NonZeroUsize};
|
use std::num::{NonZeroI64, NonZeroU64, NonZeroUsize};
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
@ -95,8 +95,19 @@ cast_to_value! {
|
|||||||
v: u32 => Value::Int(v as i64)
|
v: u32 => Value::Int(v as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
u64,
|
||||||
|
int: i64 => int.try_into().map_err(|_| {
|
||||||
|
if int < 0 {
|
||||||
|
"number must be at least zero"
|
||||||
|
} else {
|
||||||
|
"number too large"
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
}
|
||||||
|
|
||||||
cast_to_value! {
|
cast_to_value! {
|
||||||
v: i32 => Value::Int(v as i64)
|
v: u64 => Value::Int(v as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
@ -114,19 +125,38 @@ cast_to_value! {
|
|||||||
v: usize => Value::Int(v as i64)
|
v: usize => Value::Int(v as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: i32 => Value::Int(v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
u64,
|
NonZeroI64,
|
||||||
int: i64 => int.try_into().map_err(|_| {
|
int: i64 => int.try_into()
|
||||||
if int < 0 {
|
.map_err(|_| if int == 0 {
|
||||||
"number must be at least zero"
|
"number must not be zero"
|
||||||
} else {
|
} else {
|
||||||
"number too large"
|
"number too large"
|
||||||
}
|
})?,
|
||||||
})?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_to_value! {
|
cast_to_value! {
|
||||||
v: u64 => Value::Int(v as i64)
|
v: NonZeroI64 => Value::Int(v.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
NonZeroU64,
|
||||||
|
int: i64 => int
|
||||||
|
.try_into()
|
||||||
|
.and_then(|int: u64| int.try_into())
|
||||||
|
.map_err(|_| if int <= 0 {
|
||||||
|
"number must be positive"
|
||||||
|
} else {
|
||||||
|
"number too large"
|
||||||
|
})?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: NonZeroU64 => Value::Int(v.get() as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
@ -145,20 +175,6 @@ cast_to_value! {
|
|||||||
v: NonZeroUsize => Value::Int(v.get() as i64)
|
v: NonZeroUsize => Value::Int(v.get() as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
|
||||||
NonZeroI64,
|
|
||||||
int: i64 => int.try_into()
|
|
||||||
.map_err(|_| if int <= 0 {
|
|
||||||
"number must be positive"
|
|
||||||
} else {
|
|
||||||
"number too large"
|
|
||||||
})?,
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_to_value! {
|
|
||||||
v: NonZeroI64 => Value::Int(v.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
char,
|
char,
|
||||||
string: Str => {
|
string: Str => {
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
#calc.pow(2, calc.pow(2.0, 10000.0))
|
#calc.pow(2, calc.pow(2.0, 10000.0))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 15-18 the result is not a real number
|
// Error: 10-19 the result is not a real number
|
||||||
#calc.pow(-1, 0.5)
|
#calc.pow(-1, 0.5)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -107,11 +107,11 @@
|
|||||||
#calc.log(-1)
|
#calc.log(-1)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 11-12 base may not be zero, NaN, infinite, or subnormal
|
// Error: 20-21 base may not be zero, NaN, infinite, or subnormal
|
||||||
#calc.log(1, base: 0)
|
#calc.log(1, base: 0)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 11-13 the result is not a real number
|
// Error: 10-24 the result is not a real number
|
||||||
#calc.log(10, base: -1)
|
#calc.log(10, base: -1)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -120,7 +120,7 @@
|
|||||||
#test(calc.fact(5), 120)
|
#test(calc.fact(5), 120)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 12-14 the factorial result is too large
|
// Error: 11-15 the result is too large
|
||||||
#calc.fact(21)
|
#calc.fact(21)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -131,7 +131,7 @@
|
|||||||
#test(calc.perm(5, 6), 0)
|
#test(calc.perm(5, 6), 0)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 12-14 the permutation result is too large
|
// Error: 11-19 the result is too large
|
||||||
#calc.perm(21, 21)
|
#calc.perm(21, 21)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -175,5 +175,5 @@
|
|||||||
#range(4, step: "one")
|
#range(4, step: "one")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 18-19 number must be positive
|
// Error: 18-19 number must not be zero
|
||||||
#range(10, step: 0)
|
#range(10, step: 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user