diff --git a/crates/typst-utils/src/lib.rs b/crates/typst-utils/src/lib.rs index 77581860e..8488b72d5 100644 --- a/crates/typst-utils/src/lib.rs +++ b/crates/typst-utils/src/lib.rs @@ -17,7 +17,7 @@ pub use self::deferred::Deferred; pub use self::duration::format_duration; pub use self::hash::LazyHash; pub use self::pico::PicoStr; -pub use self::round::round_with_precision; +pub use self::round::{round_int_with_precision, round_with_precision}; pub use self::scalar::Scalar; use std::fmt::{Debug, Formatter}; diff --git a/crates/typst-utils/src/round.rs b/crates/typst-utils/src/round.rs index e72b45f71..3b0d282e6 100644 --- a/crates/typst-utils/src/round.rs +++ b/crates/typst-utils/src/round.rs @@ -1,17 +1,29 @@ /// Returns value with `n` digits after floating point where `n` is `precision`. -/// Standard rounding rules apply (if `n+1`th digit >= 5, round up). +/// Standard rounding rules apply (if `n+1`th digit >= 5, round away from zero). +/// +/// If `precision` is negative, returns value with `n` less significant integer +/// digits before floating point where `n` is `-precision`. Standard rounding +/// rules apply to the first remaining significant digit (if `n`th digit from +/// the floating point >= 5, round away from zero). /// /// If rounding the `value` will have no effect (e.g., it's infinite or NaN), /// returns `value` unchanged. /// +/// Note that rounding with negative precision may return plus or minus +/// infinity if the result would overflow or underflow (respectively) the range +/// of floating-point numbers. +/// /// # Examples /// /// ``` /// # use typst_utils::round_with_precision; /// let rounded = round_with_precision(-0.56553, 2); /// assert_eq!(-0.57, rounded); +/// +/// let rounded_negative = round_with_precision(823543.0, -3); +/// assert_eq!(824000.0, rounded_negative); /// ``` -pub fn round_with_precision(value: f64, precision: u8) -> f64 { +pub fn round_with_precision(value: f64, precision: i16) -> f64 { // Don't attempt to round the float if that wouldn't have any effect. // This includes infinite or NaN values, as well as integer values // with a filled mantissa (which can't have a fractional part). @@ -23,83 +35,270 @@ pub fn round_with_precision(value: f64, precision: u8) -> f64 { // `value * offset` multiplication) does not. if value.is_infinite() || value.is_nan() - || value.abs() >= (1_i64 << f64::MANTISSA_DIGITS) as f64 - || precision as u32 >= f64::DIGITS + || precision >= 0 && value.abs() >= (1_i64 << f64::MANTISSA_DIGITS) as f64 + || precision >= f64::DIGITS as i16 { return value; } - let offset = 10_f64.powi(precision.into()); - assert!((value * offset).is_finite(), "{value} * {offset} is not finite!"); - (value * offset).round() / offset + // Floats cannot have more than this amount of base-10 integer digits. + if precision < -(f64::MAX_10_EXP as i16) { + // Multiply by zero to ensure sign is kept. + return value * 0.0; + } + if precision > 0 { + let offset = 10_f64.powi(precision.into()); + assert!((value * offset).is_finite(), "{value} * {offset} is not finite!"); + (value * offset).round() / offset + } else { + // Divide instead of multiplying by a negative exponent given that + // `f64::MAX_10_EXP` is larger than `f64::MIN_10_EXP` in absolute value + // (|308| > |-307|), allowing for the precision of -308 to be used. + let offset = 10_f64.powi((-precision).into()); + (value / offset).round() * offset + } +} + +/// This is used for rounding into integer digits, and is a no-op for positive +/// `precision`. +/// +/// If `precision` is negative, returns value with `n` less significant integer +/// digits from the first digit where `n` is `-precision`. Standard rounding +/// rules apply to the first remaining significant digit (if `n`th digit from +/// the first digit >= 5, round away from zero). +/// +/// Note that this may return `None` for negative precision when rounding +/// beyond [`i64::MAX`] or [`i64::MIN`]. +/// +/// # Examples +/// +/// ``` +/// # use typst_utils::round_int_with_precision; +/// let rounded = round_int_with_precision(-154, -2); +/// assert_eq!(Some(-200), rounded); +/// +/// let rounded = round_int_with_precision(823543, -3); +/// assert_eq!(Some(824000), rounded); +/// ``` +pub fn round_int_with_precision(value: i64, precision: i16) -> Option { + if precision >= 0 { + return Some(value); + } + + let digits = -precision as u32; + let Some(ten_to_digits) = 10i64.checked_pow(digits - 1) else { + // Larger than any possible amount of integer digits. + return Some(0); + }; + + // Divide by 10^(digits - 1). + // + // We keep the last digit we want to remove as the first digit of this + // number, so we can check it with mod 10 for rounding purposes. + let truncated = value / ten_to_digits; + if truncated == 0 { + return Some(0); + } + + let rounded = if (truncated % 10).abs() >= 5 { + // Round away from zero (towards the next multiple of 10). + // + // This may overflow in the particular case of rounding MAX/MIN + // with -1. + truncated.checked_add(truncated.signum() * (10 - (truncated % 10).abs()))? + } else { + // Just replace the last digit with zero, since it's < 5. + truncated - (truncated % 10) + }; + + // Multiply back by 10^(digits - 1). + // + // May overflow / underflow, in which case we fail. + rounded.checked_mul(ten_to_digits) } #[cfg(test)] mod tests { - use super::*; + use super::{round_int_with_precision as rip, round_with_precision as rp}; #[test] fn test_round_with_precision_0() { - let round = |value| round_with_precision(value, 0); - assert_eq!(0.0, round(0.0)); - assert_eq!(-0.0, round(-0.0)); - assert_eq!(0.0, round(0.4)); - assert_eq!(-0.0, round(-0.4)); - assert_eq!(1.0, round(0.56453)); - assert_eq!(-1.0, round(-0.56453)); + let round = |value| rp(value, 0); + assert_eq!(round(0.0), 0.0); + assert_eq!(round(-0.0), -0.0); + assert_eq!(round(0.4), 0.0); + assert_eq!(round(-0.4), -0.0); + assert_eq!(round(0.56453), 1.0); + assert_eq!(round(-0.56453), -1.0); } #[test] fn test_round_with_precision_1() { - let round = |value| round_with_precision(value, 1); - assert_eq!(0.0, round(0.0)); - assert_eq!(-0.0, round(-0.0)); - assert_eq!(0.4, round(0.4)); - assert_eq!(-0.4, round(-0.4)); - assert_eq!(0.4, round(0.44)); - assert_eq!(-0.4, round(-0.44)); - assert_eq!(0.6, round(0.56453)); - assert_eq!(-0.6, round(-0.56453)); - assert_eq!(1.0, round(0.96453)); - assert_eq!(-1.0, round(-0.96453)); + let round = |value| rp(value, 1); + assert_eq!(round(0.0), 0.0); + assert_eq!(round(-0.0), -0.0); + assert_eq!(round(0.4), 0.4); + assert_eq!(round(-0.4), -0.4); + assert_eq!(round(0.44), 0.4); + assert_eq!(round(-0.44), -0.4); + assert_eq!(round(0.56453), 0.6); + assert_eq!(round(-0.56453), -0.6); + assert_eq!(round(0.96453), 1.0); + assert_eq!(round(-0.96453), -1.0); } #[test] fn test_round_with_precision_2() { - let round = |value| round_with_precision(value, 2); - assert_eq!(0.0, round(0.0)); - assert_eq!(-0.0, round(-0.0)); - assert_eq!(0.4, round(0.4)); - assert_eq!(-0.4, round(-0.4)); - assert_eq!(0.44, round(0.44)); - assert_eq!(-0.44, round(-0.44)); - assert_eq!(0.44, round(0.444)); - assert_eq!(-0.44, round(-0.444)); - assert_eq!(0.57, round(0.56553)); - assert_eq!(-0.57, round(-0.56553)); - assert_eq!(1.0, round(0.99553)); - assert_eq!(-1.0, round(-0.99553)); + let round = |value| rp(value, 2); + assert_eq!(round(0.0), 0.0); + assert_eq!(round(-0.0), -0.0); + assert_eq!(round(0.4), 0.4); + assert_eq!(round(-0.4), -0.4); + assert_eq!(round(0.44), 0.44); + assert_eq!(round(-0.44), -0.44); + assert_eq!(round(0.444), 0.44); + assert_eq!(round(-0.444), -0.44); + assert_eq!(round(0.56553), 0.57); + assert_eq!(round(-0.56553), -0.57); + assert_eq!(round(0.99553), 1.0); + assert_eq!(round(-0.99553), -1.0); + } + + #[test] + fn test_round_with_precision_negative_1() { + let round = |value| rp(value, -1); + assert_eq!(round(0.0), 0.0); + assert_eq!(round(-0.0), -0.0); + assert_eq!(round(0.4), 0.0); + assert_eq!(round(-0.4), -0.0); + assert_eq!(round(1234.5), 1230.0); + assert_eq!(round(-1234.5), -1230.0); + assert_eq!(round(1245.232), 1250.0); + assert_eq!(round(-1245.232), -1250.0); + } + + #[test] + fn test_round_with_precision_negative_2() { + let round = |value| rp(value, -2); + assert_eq!(round(0.0), 0.0); + assert_eq!(round(-0.0), -0.0); + assert_eq!(round(0.4), 0.0); + assert_eq!(round(-0.4), -0.0); + assert_eq!(round(1243.232), 1200.0); + assert_eq!(round(-1243.232), -1200.0); + assert_eq!(round(1253.232), 1300.0); + assert_eq!(round(-1253.232), -1300.0); } #[test] fn test_round_with_precision_fuzzy() { - let round = |value| round_with_precision(value, 0); - assert_eq!(f64::INFINITY, round(f64::INFINITY)); - assert_eq!(f64::NEG_INFINITY, round(f64::NEG_INFINITY)); - assert!(round(f64::NAN).is_nan()); - let max_int = (1_i64 << f64::MANTISSA_DIGITS) as f64; - let f64_digits = f64::DIGITS as u8; + let max_digits = f64::DIGITS as i16; - // max - assert_eq!(max_int, round(max_int)); - assert_eq!(0.123456, round_with_precision(0.123456, f64_digits)); - assert_eq!(max_int, round_with_precision(max_int, f64_digits)); + // Special cases. + assert_eq!(rp(f64::INFINITY, 0), f64::INFINITY); + assert_eq!(rp(f64::NEG_INFINITY, 0), f64::NEG_INFINITY); + assert!(rp(f64::NAN, 0).is_nan()); - // max - 1 - assert_eq!(max_int - 1f64, round(max_int - 1f64)); - assert_eq!(0.123456, round_with_precision(0.123456, f64_digits - 1)); - assert_eq!(max_int - 1f64, round_with_precision(max_int - 1f64, f64_digits)); - assert_eq!(max_int, round_with_precision(max_int, f64_digits - 1)); - assert_eq!(max_int - 1f64, round_with_precision(max_int - 1f64, f64_digits - 1)); + // Max + assert_eq!(rp(max_int, 0), max_int); + assert_eq!(rp(0.123456, max_digits), 0.123456); + assert_eq!(rp(max_int, max_digits), max_int); + + // Max - 1 + assert_eq!(rp(max_int - 1.0, 0), max_int - 1.0); + assert_eq!(rp(0.123456, max_digits - 1), 0.123456); + assert_eq!(rp(max_int - 1.0, max_digits), max_int - 1.0); + assert_eq!(rp(max_int, max_digits - 1), max_int); + assert_eq!(rp(max_int - 1.0, max_digits - 1), max_int - 1.0); + } + + #[test] + fn test_round_with_precision_fuzzy_negative() { + let exp10 = |exponent: i16| 10_f64.powi(exponent.into()); + let max_digits = f64::MAX_10_EXP as i16; + let max_up = max_digits + 1; + let max_down = max_digits - 1; + + // Special cases. + assert_eq!(rp(f64::INFINITY, -1), f64::INFINITY); + assert_eq!(rp(f64::NEG_INFINITY, -1), f64::NEG_INFINITY); + assert!(rp(f64::NAN, -1).is_nan()); + + // Max + assert_eq!(rp(f64::MAX, -max_digits), f64::INFINITY); + assert_eq!(rp(f64::MIN, -max_digits), f64::NEG_INFINITY); + assert_eq!(rp(1.66 * exp10(max_digits), -max_digits), f64::INFINITY); + assert_eq!(rp(-1.66 * exp10(max_digits), -max_digits), f64::NEG_INFINITY); + assert_eq!(rp(1.66 * exp10(max_down), -max_digits), 0.0); + assert_eq!(rp(-1.66 * exp10(max_down), -max_digits), -0.0); + assert_eq!(rp(1234.5678, -max_digits), 0.0); + assert_eq!(rp(-1234.5678, -max_digits), -0.0); + + // Max + 1 + assert_eq!(rp(f64::MAX, -max_up), 0.0); + assert_eq!(rp(f64::MIN, -max_up), -0.0); + assert_eq!(rp(1.66 * exp10(max_digits), -max_up), 0.0); + assert_eq!(rp(-1.66 * exp10(max_digits), -max_up), -0.0); + assert_eq!(rp(1.66 * exp10(max_down), -max_up), 0.0); + assert_eq!(rp(-1.66 * exp10(max_down), -max_up), -0.0); + assert_eq!(rp(1234.5678, -max_up), 0.0); + assert_eq!(rp(-1234.5678, -max_up), -0.0); + + // Max - 1 + assert_eq!(rp(f64::MAX, -max_down), f64::INFINITY); + assert_eq!(rp(f64::MIN, -max_down), f64::NEG_INFINITY); + assert_eq!(rp(1.66 * exp10(max_down), -max_down), 2.0 * exp10(max_down)); + assert_eq!(rp(-1.66 * exp10(max_down), -max_down), -2.0 * exp10(max_down)); + assert_eq!(rp(1234.5678, -max_down), 0.0); + assert_eq!(rp(-1234.5678, -max_down), -0.0); + + // Must be approx equal to 1.7e308. Using some division and flooring + // to avoid weird results due to imprecision. + assert_eq!( + (rp(1.66 * exp10(max_digits), -max_down) / exp10(max_down)).floor(), + 17.0, + ); + assert_eq!( + (rp(-1.66 * exp10(max_digits), -max_down) / exp10(max_down)).floor(), + -17.0, + ); + } + + #[test] + fn test_round_int_with_precision_positive() { + assert_eq!(rip(0, 0), Some(0)); + assert_eq!(rip(10, 0), Some(10)); + assert_eq!(rip(23, 235), Some(23)); + assert_eq!(rip(i64::MAX, 235), Some(i64::MAX)); + } + + #[test] + fn test_round_int_with_precision_negative_1() { + let round = |value| rip(value, -1); + assert_eq!(round(0), Some(0)); + assert_eq!(round(3), Some(0)); + assert_eq!(round(5), Some(10)); + assert_eq!(round(13), Some(10)); + assert_eq!(round(1234), Some(1230)); + assert_eq!(round(-1234), Some(-1230)); + assert_eq!(round(1245), Some(1250)); + assert_eq!(round(-1245), Some(-1250)); + assert_eq!(round(i64::MAX), None); + assert_eq!(round(i64::MIN), None); + } + + #[test] + fn test_round_int_with_precision_negative_2() { + let round = |value| rip(value, -2); + assert_eq!(round(0), Some(0)); + assert_eq!(round(3), Some(0)); + assert_eq!(round(5), Some(0)); + assert_eq!(round(13), Some(0)); + assert_eq!(round(1245), Some(1200)); + assert_eq!(round(-1245), Some(-1200)); + assert_eq!(round(1253), Some(1300)); + assert_eq!(round(-1253), Some(-1300)); + assert_eq!(round(i64::MAX), Some(i64::MAX - 7)); + assert_eq!(round(i64::MIN), Some(i64::MIN + 8)); } } diff --git a/crates/typst/src/foundations/calc.rs b/crates/typst/src/foundations/calc.rs index 0e9ef1468..5c818368d 100644 --- a/crates/typst/src/foundations/calc.rs +++ b/crates/typst/src/foundations/calc.rs @@ -10,7 +10,7 @@ use crate::eval::ops; use crate::foundations::{cast, func, Decimal, IntoValue, Module, Scope, Value}; use crate::layout::{Angle, Fr, Length, Ratio}; use crate::syntax::{Span, Spanned}; -use crate::utils::round_with_precision; +use crate::utils::{round_int_with_precision, round_with_precision}; /// A module with calculation definitions. pub fn module() -> Module { @@ -714,10 +714,13 @@ pub fn fract( } } -/// Rounds a number to the nearest integer. +/// Rounds a number to the nearest integer away from zero. /// /// Optionally, a number of decimal places can be specified. /// +/// If the number of digits is 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 @@ -725,29 +728,48 @@ pub fn fract( /// `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 /// #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")) /// #calc.round(3.1415, digits: 2) /// ``` #[func] pub fn round( /// The number to round. value: DecNum, - /// The number of decimal places. Must not be negative. + /// 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: u32, -) -> DecNum { + digits: i64, +) -> StrResult { match value { - DecNum::Int(n) => DecNum::Int(n), + DecNum::Int(n) => Ok(DecNum::Int( + round_int_with_precision(n, digits.saturating_as::()) + .ok_or_else(too_large)?, + )), DecNum::Float(n) => { - DecNum::Float(round_with_precision(n, digits.saturating_as::())) + Ok(DecNum::Float(round_with_precision(n, digits.saturating_as::()))) } - DecNum::Decimal(n) => DecNum::Decimal(n.round(digits)), + DecNum::Decimal(n) => Ok(DecNum::Decimal( + n.round(digits.saturating_as::()).ok_or_else(too_large)?, + )), } } diff --git a/crates/typst/src/foundations/decimal.rs b/crates/typst/src/foundations/decimal.rs index f2cef59bf..6ba4c1a68 100644 --- a/crates/typst/src/foundations/decimal.rs +++ b/crates/typst/src/foundations/decimal.rs @@ -95,6 +95,8 @@ pub struct Decimal(rust_decimal::Decimal); impl Decimal { pub const ZERO: Self = Self(rust_decimal::Decimal::ZERO); pub const ONE: Self = Self(rust_decimal::Decimal::ONE); + pub const MIN: Self = Self(rust_decimal::Decimal::MIN); + pub const MAX: Self = Self(rust_decimal::Decimal::MAX); /// Whether this decimal value is zero. pub const fn is_zero(self) -> bool { @@ -146,11 +148,46 @@ impl Decimal { /// Rounds this decimal up to the specified amount of digits with the /// traditional rounding rules, using the "midpoint away from zero" /// strategy (6.5 -> 7, -6.5 -> -7). - pub fn round(self, digits: u32) -> Self { - Self(self.0.round_dp_with_strategy( - digits, + /// + /// If given a negative amount of digits, rounds to integer digits instead + /// with the same rounding strategy. For example, rounding to -3 digits + /// will turn 34567.89 into 35000.00 and -34567.89 into -35000.00. + /// + /// Note that this can return `None` when using negative digits where the + /// rounded number would overflow the available range for decimals. + pub fn round(self, digits: i32) -> Option { + // Positive digits can be handled by just rounding with rust_decimal. + if let Ok(positive_digits) = u32::try_from(digits) { + return Some(Self(self.0.round_dp_with_strategy( + positive_digits, + rust_decimal::RoundingStrategy::MidpointAwayFromZero, + ))); + } + + // We received negative digits, so we round to integer digits. + let mut num = self.0; + let old_scale = num.scale(); + let digits = -digits as u32; + + let (Ok(_), Some(ten_to_digits)) = ( + // Same as dividing by 10^digits. + num.set_scale(old_scale + digits), + rust_decimal::Decimal::TEN.checked_powi(digits as i64), + ) else { + // Scaling more than any possible amount of integer digits. + let mut zero = rust_decimal::Decimal::ZERO; + zero.set_sign_negative(self.is_negative()); + return Some(Self(zero)); + }; + + // Round to this integer digit. + num = num.round_dp_with_strategy( + 0, rust_decimal::RoundingStrategy::MidpointAwayFromZero, - )) + ); + + // Multiply by 10^digits again, which can overflow and fail. + num.checked_mul(ten_to_digits).map(Self) } /// Attempts to add two decimals. @@ -426,4 +463,33 @@ mod tests { assert_eq!(a, b); assert_ne!(hash128(&a), hash128(&b)); } + + #[track_caller] + fn test_round(value: &str, digits: i32, expected: &str) { + assert_eq!( + Decimal::from_str(value).unwrap().round(digits), + Some(Decimal::from_str(expected).unwrap()), + ); + } + + #[test] + fn test_decimal_positive_round() { + test_round("312.55553", 0, "313.00000"); + test_round("312.55553", 3, "312.556"); + test_round("312.5555300000", 3, "312.556"); + test_round("-312.55553", 3, "-312.556"); + test_round("312.55553", 28, "312.55553"); + test_round("312.55553", 2341, "312.55553"); + test_round("-312.55553", 2341, "-312.55553"); + } + + #[test] + fn test_decimal_negative_round() { + test_round("4596.55553", -1, "4600"); + test_round("4596.555530000000", -1, "4600"); + test_round("-4596.55553", -3, "-5000"); + test_round("4596.55553", -28, "0"); + test_round("-4596.55553", -2341, "0"); + assert_eq!(Decimal::MAX.round(-1), None); + } } diff --git a/crates/typst/src/foundations/repr.rs b/crates/typst/src/foundations/repr.rs index 551046042..145109682 100644 --- a/crates/typst/src/foundations/repr.rs +++ b/crates/typst/src/foundations/repr.rs @@ -85,7 +85,7 @@ pub fn format_float( unit: &str, ) -> EcoString { if let Some(p) = precision { - value = round_with_precision(value, p); + value = round_with_precision(value, p as i16); } // Debug for f64 always prints a decimal separator, while Display only does // when necessary. diff --git a/tests/suite/foundations/calc.typ b/tests/suite/foundations/calc.typ index 4cf7ab2a4..5726dafa1 100644 --- a/tests/suite/foundations/calc.typ +++ b/tests/suite/foundations/calc.typ @@ -4,18 +4,23 @@ #test(type(calc.round(3.1415, digits: 2)), float) #test(type(calc.round(5, digits: 2)), int) #test(type(calc.round(decimal("3.1415"), digits: 2)), decimal) +#test(type(calc.round(314.15, digits: -2)), float) +#test(type(calc.round(523, digits: -2)), int) +#test(type(calc.round(decimal("314.15"), digits: -2)), decimal) --- calc-round-large-inputs --- #test(calc.round(31114, digits: 4000000000), 31114) #test(calc.round(9223372036854775807, digits: 12), 9223372036854775807) +#test(calc.round(9223372036854775807, digits: -20), 0) #test(calc.round(238959235.129590203, digits: 4000000000), 238959235.129590203) #test(calc.round(1.7976931348623157e+308, digits: 12), 1.7976931348623157e+308) +#test(calc.round(1.7976931348623157e+308, digits: -308), float.inf) +#test(calc.round(-1.7976931348623157e+308, digits: -308), -float.inf) +#test(calc.round(12.34, digits: -312), 0.0) #test(calc.round(decimal("238959235.129590203"), digits: 4000000000), decimal("238959235.129590203")) #test(calc.round(decimal("79228162514264337593543950335"), digits: 12), decimal("79228162514264337593543950335")) - ---- calc-round-negative-digits --- -// Error: 29-31 number must be at least zero -#calc.round(243.32, digits: -2) +#test(calc.round(decimal("79228162514264337593543950335"), digits: -50), decimal("0")) +#test(calc.round(decimal("-79228162514264337593543950335"), digits: -2), decimal("-79228162514264337593543950300")) --- calc-abs --- // Test the `abs` function. @@ -331,6 +336,22 @@ // Error: 2-47 the result is too large #calc.floor(decimal("-9223372036854775809.5")) +--- calc-round-int-too-large --- +// Error: 2-45 the result is too large +#calc.round(9223372036854775807, digits: -1) + +--- calc-round-int-negative-too-large --- +// Error: 2-46 the result is too large +#calc.round(-9223372036854775807, digits: -1) + +--- calc-round-decimal-too-large --- +// Error: 2-66 the result is too large +#calc.round(decimal("79228162514264337593543950335"), digits: -1) + +--- calc-round-decimal-negative-too-large --- +// Error: 2-67 the result is too large +#calc.round(decimal("-79228162514264337593543950335"), digits: -1) + --- calc-min-nothing --- // Error: 2-12 expected at least one value #calc.min()