Added converge builtin function
This commit is contained in:
parent
e53832ca4b
commit
1e5e9a3ae9
@ -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?
|
||||
-----------------------
|
||||
|
@ -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<f64, Error> {
|
||||
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<f64, Error> {
|
||||
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<f64, Error> {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
//!
|
||||
|
Loading…
Reference in New Issue
Block a user