2021-05-17 16:51:16 +02:00
|
|
|
use crate::ast::Expr;
|
2021-05-17 20:36:53 +02:00
|
|
|
use crate::ast::Identifier;
|
2021-05-17 16:51:16 +02:00
|
|
|
use crate::ast::Stmt;
|
|
|
|
use crate::interpreter;
|
|
|
|
use crate::kalk_num::KalkNum;
|
|
|
|
use crate::lexer::TokenKind;
|
|
|
|
use crate::parser::CalcError;
|
|
|
|
|
2021-05-17 20:36:53 +02:00
|
|
|
pub fn derive_func(
|
|
|
|
context: &mut interpreter::Context,
|
|
|
|
name: &Identifier,
|
|
|
|
argument: KalkNum,
|
|
|
|
) -> Result<KalkNum, CalcError> {
|
|
|
|
const H: f64 = 0.000001;
|
|
|
|
let unit = &argument.unit.to_string();
|
2021-05-17 23:09:59 +02:00
|
|
|
|
2021-05-17 20:36:53 +02:00
|
|
|
let argument_with_h = Expr::Literal(argument.clone().add(context, H.into()).to_f64());
|
2021-05-17 21:27:11 +02:00
|
|
|
let argument_without_h = Expr::Literal(argument.sub(context, H.into()).to_f64());
|
2021-05-17 23:09:59 +02:00
|
|
|
let new_identifier = Identifier::from_name_and_primes(&name.pure_name, name.prime_count - 1);
|
2021-05-17 20:36:53 +02:00
|
|
|
|
2021-05-17 23:09:59 +02:00
|
|
|
let f_x_h = interpreter::eval_fn_call_expr(context, &new_identifier, &[argument_with_h], unit)?;
|
|
|
|
let f_x =
|
|
|
|
interpreter::eval_fn_call_expr(context, &new_identifier, &[argument_without_h], unit)?;
|
2021-05-17 20:36:53 +02:00
|
|
|
|
2021-05-17 23:55:20 +02:00
|
|
|
Ok(round(
|
|
|
|
f_x_h.sub(context, f_x).div(context, (2f64 * H).into()),
|
|
|
|
))
|
2021-05-17 20:36:53 +02:00
|
|
|
}
|
|
|
|
|
2021-05-17 16:51:16 +02:00
|
|
|
pub fn integrate(
|
|
|
|
context: &mut interpreter::Context,
|
2021-05-17 20:36:53 +02:00
|
|
|
a: &Expr,
|
|
|
|
b: &Expr,
|
|
|
|
expr: &Expr,
|
2021-05-17 16:51:16 +02:00
|
|
|
) -> Result<KalkNum, CalcError> {
|
|
|
|
let mut integration_variable: Option<&str> = None;
|
|
|
|
|
|
|
|
// integral(a, b, expr dx)
|
2021-05-17 20:36:53 +02:00
|
|
|
if let Expr::Binary(_, TokenKind::Star, right) = expr {
|
2021-05-17 16:51:16 +02:00
|
|
|
if let Expr::Var(right_name) = &**right {
|
2021-05-17 20:36:53 +02:00
|
|
|
if right_name.full_name.starts_with("d") {
|
2021-05-17 16:51:16 +02:00
|
|
|
// Take the value, but remove the d, so that only eg. x is left from dx
|
2021-05-17 20:36:53 +02:00
|
|
|
integration_variable = Some(&right_name.full_name[1..]);
|
2021-05-17 16:51:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if integration_variable.is_none() {
|
|
|
|
return Err(CalcError::ExpectedDx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value.
|
|
|
|
context.symbol_table.set(Stmt::VarDecl(
|
2021-05-17 20:36:53 +02:00
|
|
|
Identifier::from_full_name(&format!("d{}", integration_variable.unwrap())),
|
2021-05-17 16:51:16 +02:00
|
|
|
Box::new(Expr::Literal(1f64)),
|
|
|
|
));
|
|
|
|
|
2021-05-17 23:55:20 +02:00
|
|
|
Ok(round(simpsons_rule(
|
|
|
|
context,
|
|
|
|
a,
|
|
|
|
b,
|
|
|
|
expr,
|
|
|
|
integration_variable.unwrap(),
|
|
|
|
)?))
|
2021-05-17 18:05:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Composite Simpson's 3/8 rule
|
|
|
|
fn simpsons_rule(
|
|
|
|
context: &mut interpreter::Context,
|
|
|
|
a_expr: &Expr,
|
|
|
|
b_expr: &Expr,
|
|
|
|
expr: &Expr,
|
|
|
|
integration_variable: &str,
|
|
|
|
) -> Result<KalkNum, CalcError> {
|
|
|
|
let mut result = KalkNum::default();
|
2021-05-17 16:51:16 +02:00
|
|
|
|
2021-05-17 18:05:22 +02:00
|
|
|
const N: i32 = 900;
|
|
|
|
let a = interpreter::eval_expr(context, a_expr, "")?.value.to_f64();
|
|
|
|
let b = interpreter::eval_expr(context, b_expr, "")?.value.to_f64();
|
|
|
|
let h = (b - a) / N as f64;
|
|
|
|
for i in 0..=N {
|
2021-05-17 16:51:16 +02:00
|
|
|
context.symbol_table.set(Stmt::VarDecl(
|
2021-05-17 20:36:53 +02:00
|
|
|
Identifier::from_full_name(integration_variable),
|
2021-05-17 18:05:22 +02:00
|
|
|
Box::new(Expr::Literal(a + i as f64 * h)),
|
2021-05-17 16:51:16 +02:00
|
|
|
));
|
|
|
|
|
2021-05-17 20:36:53 +02:00
|
|
|
let factor = match i {
|
|
|
|
0 | N => 1,
|
|
|
|
_ if i % 3 == 0 => 2,
|
|
|
|
_ => 3,
|
2021-05-17 18:05:22 +02:00
|
|
|
};
|
2021-05-17 16:51:16 +02:00
|
|
|
|
2021-05-17 18:05:22 +02:00
|
|
|
// factor * f(x_n)
|
|
|
|
result.value += factor * interpreter::eval_expr(context, expr, "")?.value;
|
|
|
|
}
|
2021-05-17 16:51:16 +02:00
|
|
|
|
2021-05-17 18:05:22 +02:00
|
|
|
result.value *= (3f64 * h) / 8f64;
|
2021-05-17 16:51:16 +02:00
|
|
|
|
2021-05-17 18:05:22 +02:00
|
|
|
Ok(result)
|
2021-05-17 16:51:16 +02:00
|
|
|
}
|
2021-05-17 23:55:20 +02:00
|
|
|
|
|
|
|
/// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc.
|
|
|
|
fn round(num: KalkNum) -> KalkNum {
|
|
|
|
let fract = num.value.clone().fract();
|
|
|
|
let floored = num.value.clone().floor();
|
|
|
|
|
|
|
|
// If it's zero something, don't do the rounding as aggressively.
|
|
|
|
let (limit_floor, limit_ceil) = if floored.clone() == 0 {
|
|
|
|
(-15, -5)
|
|
|
|
} else {
|
|
|
|
(-4, -6)
|
|
|
|
};
|
|
|
|
|
|
|
|
if fract.clone().log10() < limit_floor {
|
|
|
|
// If eg. 0.00xxx
|
|
|
|
return KalkNum::new(floored, &num.unit);
|
|
|
|
} else if (1f64 - fract).log10() < limit_ceil {
|
|
|
|
// If eg. 0.999
|
|
|
|
return KalkNum::new(num.value.clone().ceil(), &num.unit);
|
|
|
|
} else {
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
}
|