Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/arithmetic_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use super::*;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ArithmeticError {
DivisionByZero,
ModuloByZero,
ZeroToNegativePower,
}

impl Display for ArithmeticError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::DivisionByZero => "Division by zero",
Self::ModuloByZero => "Modulo by zero",
Self::ZeroToNegativePower => "Zero cannot be raised to a negative power",
})
}
}

impl std::error::Error for ArithmeticError {}
27 changes: 21 additions & 6 deletions src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ fn acot<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
)
.0,
)
.div(&Number::from(2_i64), payload.config);
.div(&Number::from(2_i64), payload.config)
.unwrap();

Ok(Value::Number(
pi_div_2.sub(&argument.atan(payload.config), payload.config),
Expand All @@ -266,7 +267,9 @@ fn acsc<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
));
}

let reciprocal = Number::from(1_i64).div(&argument, payload.config);
let reciprocal = Number::from(1_i64)
.div(&argument, payload.config)
.map_err(|error| Error::new(payload.span, error.to_string()))?;

Ok(Value::Number(reciprocal.asin(payload.config)))
}
Expand Down Expand Up @@ -299,7 +302,9 @@ fn asec<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
));
}

let reciprocal = Number::from(1_i64).div(&argument, payload.config);
let reciprocal = Number::from(1_i64)
.div(&argument, payload.config)
.map_err(|error| Error::new(payload.span, error.to_string()))?;

Ok(Value::Number(reciprocal.acos(payload.config)))
}
Expand Down Expand Up @@ -349,6 +354,7 @@ fn constant_phi(config: Config) -> Number {
Number::from(1_i64)
.add(&Number::from(5_i64).sqrt(config), config)
.div(&Number::from(2_i64), config)
.unwrap()
}

fn constant_pi(config: Config) -> Number {
Expand Down Expand Up @@ -394,7 +400,10 @@ fn cot<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
));
}

Ok(Value::Number(Number::from(1_i64).div(&tan, payload.config)))
Number::from(1_i64)
.div(&tan, payload.config)
.map(Value::Number)
.map_err(|error| Error::new(payload.span, error.to_string()))
}

fn csc<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
Expand All @@ -409,7 +418,10 @@ fn csc<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
));
}

Ok(Value::Number(Number::from(1_i64).div(&sin, payload.config)))
Number::from(1_i64)
.div(&sin, payload.config)
.map(Value::Number)
.map_err(|error| Error::new(payload.span, error.to_string()))
}

fn e<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
Expand Down Expand Up @@ -760,7 +772,10 @@ fn sec<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
return Err(Error::new(payload.span, "Cannot compute sec of π/2 + nπ"));
}

Ok(Value::Number(Number::from(1_i64).div(&cos, payload.config)))
Number::from(1_i64)
.div(&cos, payload.config)
.map(Value::Number)
.map_err(|error| Error::new(payload.span, error.to_string()))
}

fn sin<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result<Value<'a>, Error> {
Expand Down
36 changes: 12 additions & 24 deletions src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,10 @@ impl<'a> Evaluator<'a> {
let (lhs_num, rhs_num) =
(lhs_val.number(lhs.1)?, rhs_val.number(rhs.1)?);

if rhs_num.is_zero() {
return Err(Error::new(rhs.1, "Division by zero"));
}

Ok(Value::Number(
lhs_num.div(&rhs_num, self.environment.config),
))
lhs_num
.div(&rhs_num, self.environment.config)
.map(Value::Number)
.map_err(|error| Error::new(rhs.1, error.to_string()))
}
Expression::BinaryOp(BinaryOp::Equal, lhs, rhs) => Ok(Value::Boolean(
self.evaluate_expression(lhs)? == self.evaluate_expression(rhs)?,
Expand Down Expand Up @@ -241,13 +238,10 @@ impl<'a> Evaluator<'a> {
let (lhs_num, rhs_num) =
(lhs_val.number(lhs.1)?, rhs_val.number(rhs.1)?);

if rhs_num.is_zero() {
return Err(Error::new(rhs.1, "Modulo by zero"));
}

Ok(Value::Number(
lhs_num.rem(&rhs_num, self.environment.config),
))
lhs_num
.rem(&rhs_num, self.environment.config)
.map(Value::Number)
.map_err(|error| Error::new(rhs.1, error.to_string()))
}
Expression::BinaryOp(BinaryOp::Multiply, lhs, rhs) => Ok(Value::Number(
self.evaluate_expression(lhs)?.number(lhs.1)?.mul(
Expand All @@ -267,16 +261,10 @@ impl<'a> Evaluator<'a> {
let (lhs_num, rhs_num) =
(lhs_val.number(lhs.1)?, rhs_val.number(rhs.1)?);

if lhs_num.is_zero() && rhs_num.is_negative() {
return Err(Error::new(
rhs.1,
"Zero cannot be raised to a negative power",
));
}

Ok(Value::Number(
lhs_num.pow(&rhs_num, self.environment.config),
))
lhs_num
.pow(&rhs_num, self.environment.config)
.map(Value::Number)
.map_err(|error| Error::new(rhs.1, error.to_string()))
}
Expression::BinaryOp(BinaryOp::Subtract, lhs, rhs) => Ok(Value::Number(
self.evaluate_expression(lhs)?.number(lhs.1)?.sub(
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use {
};

pub use crate::{
arithmetic_error::ArithmeticError,
builtin::Builtin,
builtin_arity::BuiltinArity,
builtin_function::BuiltinFunction,
Expand All @@ -48,6 +49,7 @@ pub type Spanned<T> = (T, Span);

type Result<T = (), E = anyhow::Error> = std::result::Result<T, E>;

mod arithmetic_error;
pub mod ast;
mod builtin;
mod builtin_arity;
Expand Down
81 changes: 66 additions & 15 deletions src/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,27 @@ impl Number {
}
}

#[must_use]
pub fn div(&self, rhs: &Self, config: Config) -> Self {
if let (Self::Exact(lhs), Self::Exact(rhs)) = (self, rhs) {
Self::Exact((lhs / rhs).complete())
/// # Errors
///
/// Returns [`ArithmeticError::DivisionByZero`] if `rhs` is zero.
pub fn div(
&self,
rhs: &Self,
config: Config,
) -> std::result::Result<Self, ArithmeticError> {
if rhs.is_zero() {
Err(ArithmeticError::DivisionByZero)
} else if let (Self::Exact(lhs), Self::Exact(rhs)) = (self, rhs) {
Ok(Self::Exact((lhs / rhs).complete()))
} else {
Self::Approx(
Ok(Self::Approx(
Float::with_val_round(
config.precision(),
&self.to_float(config) / &rhs.to_float(config),
config.rounding_mode,
)
.0,
)
))
}
}

Expand Down Expand Up @@ -209,25 +217,46 @@ impl Number {
}
}

#[must_use]
pub fn pow(&self, rhs: &Self, config: Config) -> Self {
/// # Errors
///
/// Returns [`ArithmeticError::ZeroToNegativePower`] if `self` is zero and
/// `rhs` is negative.
pub fn pow(
&self,
rhs: &Self,
config: Config,
) -> std::result::Result<Self, ArithmeticError> {
if self.is_zero() && rhs.is_negative() {
return Err(ArithmeticError::ZeroToNegativePower);
}

match (self, rhs) {
(Self::Exact(lhs), Self::Exact(exponent)) => {
if exponent.is_integer()
&& let Some(exponent) = exponent.numer().to_i32()
{
return Self::Exact(lhs.clone().pow(exponent));
return Ok(Self::Exact(lhs.clone().pow(exponent)));
}

self.approx_pow(rhs, config)
Ok(self.approx_pow(rhs, config))
}
_ => self.approx_pow(rhs, config),
_ => Ok(self.approx_pow(rhs, config)),
}
}

#[must_use]
pub fn rem(&self, rhs: &Self, config: Config) -> Self {
self.sub(&self.div(rhs, config).floor().mul(rhs, config), config)
/// # Errors
///
/// Returns [`ArithmeticError::ModuloByZero`] if `rhs` is zero.
pub fn rem(
&self,
rhs: &Self,
config: Config,
) -> std::result::Result<Self, ArithmeticError> {
if rhs.is_zero() {
return Err(ArithmeticError::ModuloByZero);
}

Ok(self.sub(&self.div(rhs, config)?.floor().mul(rhs, config), config))
}

#[must_use]
Expand Down Expand Up @@ -452,7 +481,8 @@ mod tests {
fn display_approx_configured_digits() {
let number = Number::from(2_i64)
.to_approx(Config::default())
.div(&Number::from(5_555_222_222_222_i64), Config::default());
.div(&Number::from(5_555_222_222_222_i64), Config::default())
.unwrap();

let config = Config {
digits: NonZeroUsize::new(4).unwrap(),
Expand Down Expand Up @@ -513,6 +543,7 @@ mod tests {
Number::from(2_i64)
.to_approx(Config::default())
.div(&Number::from(5_555_222_222_222_i64), Config::default())
.unwrap()
.to_string(),
"3.600216012960922e-13"
);
Expand Down Expand Up @@ -556,6 +587,26 @@ mod tests {
assert!(greater > approx);
}

#[test]
fn undefined_exact_arithmetic_returns_error() {
let zero = Number::from(0_i64);

assert_eq!(
Number::from(1_i64).div(&zero, Config::default()),
Err(ArithmeticError::DivisionByZero)
);

assert_eq!(
Number::from(1_i64).rem(&zero, Config::default()),
Err(ArithmeticError::ModuloByZero)
);

assert_eq!(
zero.pow(&Number::from(-1_i64), Config::default()),
Err(ArithmeticError::ZeroToNegativePower)
);
}

#[test]
fn zero_precision_uses_minimum() {
let config = Config {
Expand Down
Loading