From 1e5e9a3ae9af8841aca59a61d3485c04f819a27d Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 15 Mar 2019 12:04:01 +0200 Subject: [PATCH] Added converge builtin function --- README.md | 3 +- src/builtin/mod.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aff3c07..2d42200 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Features Supported operators: `!` `!=` `""` `''` `()` `[]` `,` `>` `<` `>=` `<=` `==` `+` `-` `*` `/` `%` `&&` `||` `n..m`. -Built-in functions: `min()` `max()` `len()` `is_empty()` `array()`. +Built-in functions: `min()` `max()` `len()` `is_empty()` `array()` `converge()`. +See the `builtin` module for a detailed description of each. Where can eval be used? ----------------------- diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index b985fc8..60f0156 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -14,10 +14,35 @@ impl BuiltIn { functions.insert("len".to_owned(), create_len_fuction()); functions.insert("is_empty".to_owned(), create_is_empty_fuction()); functions.insert("array".to_owned(), create_array_function()); + functions.insert("converge".to_owned(), create_converge_function()); functions } } +fn expect_number_f64(value: &Value) -> Result { + if let Some(number) = value.as_f64() { + Ok(number) + } else { + Err(Error::Custom(format!("Expected number that can be represented as f64. But the given is: {:?}", value))) + } +} + +fn expect_normal_f64(number: f64) -> Result { + if number.is_normal() { + Ok(number) + } else { + Err(Error::Custom(format!("Expected normal number. But the given is: {:?}", number))) + } +} + +fn expect_finite_f64(number: f64) -> Result { + if number.is_finite() { + Ok(number) + } else { + Err(Error::Custom(format!("Expected finite number. But the given is: {:?}", number))) + } +} + #[derive(PartialEq)] enum Compare { Min, @@ -116,3 +141,60 @@ fn create_len_fuction() -> Function { fn create_array_function() -> Function { Function::new(|values| Ok(to_value(values))) } + +/// Converges exponentially agains `conv_y`, starting from `start_y`. +/// The first four parameters parameterize a function `f`, the last one is the `x`-value where the function should be evaluated. +/// The result of the call is `f(x)`. +/// +/// The parameters `start_x` and `start_y` set the starting point of the function. +/// It holds that `f(start_x) = start_y`. +/// The parameter `conv_y` is the convergence target value. +/// It is never reached (assuming no numerical errors). +/// The parameter `step_x` is the "speed" of the convergence, that is how fast the function converges against `conv_y`. +/// In detail, the absolute difference between `start_y` and `conv_y` halves if `x` is increased by `step_x`. +/// +/// All parameters are expected to be numbers. +/// The parameters `start_x`, `start_y` and `conv_y` are expected to be finite. +/// The parameter `step_x` is expected to be `normal`. +/// +/// # Examples +/// +/// The function `2^(-x)` is expressed as `conv(0, 1, 1, 0, x)`. +/// This is the same as `converge(1, 0.5, 1, 0, x)`. +fn create_converge_function() -> Function { + Function { + max_args: Some(5), + min_args: Some(5), + compiled: Box::new(|values| { + let start_x = expect_finite_f64(expect_number_f64(&values[0])?)?; + let start_y = expect_finite_f64(expect_number_f64(&values[1])?)?; + let step_x = expect_normal_f64(expect_number_f64(&values[2])?)?; + let conv_y = expect_finite_f64(expect_number_f64(&values[3])?)?; + let x = expect_number_f64(&values[4])?; + + let units = (x - start_x) / step_x; + let interpolation_factor = 2.0_f64.powf(-units); + Ok(to_value(interpolation_factor * start_y + (1.0 - interpolation_factor) * conv_y)) + }), + } +} + +#[cfg(test)] +mod test { + use crate::{Expr, to_value}; + + #[test] + fn test_conv() { + let valid_test_cases = vec![ + ("converge(0, 4, 10, 0, 0)", "4.0"), + ("converge(0, 4, 10, 0, 10)", "2.0"), + ("converge(0, 4, 10, 0, 5)", "2.8284271247461900976033774484193961571393437507538961"), + ("converge(0, 4, 10, 0, 20)", "1.0"), + ("converge(0, 4, 10, 0, 0-10)", "8.0"), + ]; + + for (term, result) in valid_test_cases { + assert_eq!(Expr::new(format!("({term} < {result} * 1.0001) && ({term} > {result} / 1.0001)", term = term, result = result)).exec(), Ok(to_value(true))); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 089dbe1..f116d3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,8 @@ //! Supported operators: `!` `!=` `""` `''` `()` `[]` `.` `,` `>` `<` `>=` `<=` //! `==` `+` `-` `*` `/` `%` `&&` `||` `n..m`. //! -//! Built-in functions: `min()` `max()` `len()` `is_empty()` `array()`. +//! Built-in functions: `min()` `max()` `len()` `is_empty()` `array()` `converge()`. +//! See the `builtin` module for a detailed description of each. //! //! ## Examples //!