Changed KalkNum to an enum, KalkValue, creating a better type system

This commit is contained in:
PaddiM8 2022-01-05 01:08:08 +01:00
parent 83f20e035c
commit 92521f842b
17 changed files with 1482 additions and 1252 deletions

View File

@ -68,19 +68,19 @@ impl Identifier {
}
}
pub fn build_literal_ast(kalk_num: &crate::kalk_num::KalkNum) -> Expr {
if kalk_num.has_imaginary() {
pub fn build_literal_ast(kalk_value: &crate::kalk_value::KalkValue) -> Expr {
if kalk_value.has_imaginary() {
Expr::Binary(
Box::new(Expr::Literal(kalk_num.to_f64())),
Box::new(Expr::Literal(kalk_value.to_f64())),
TokenKind::Plus,
Box::new(Expr::Binary(
Box::new(Expr::Literal(kalk_num.imaginary_to_f64())),
Box::new(Expr::Literal(kalk_value.imaginary_to_f64())),
TokenKind::Star,
Box::new(Expr::Var(Identifier::from_full_name("i"))),
)),
)
} else {
Expr::Literal(kalk_num.to_f64())
Expr::Literal(kalk_value.to_f64())
}
}

View File

@ -0,0 +1,73 @@
use wasm_bindgen::prelude::wasm_bindgen;
use crate::kalk_value::{ComplexNumberType, KalkValue, ScientificNotation};
#[wasm_bindgen]
pub struct CalculationResult {
value: KalkValue,
radix: u8,
}
// Wraps around KalkValue since enums don't work
// with the javascript bindings.
#[wasm_bindgen]
impl CalculationResult {
pub(crate) fn new(value: KalkValue, radix: u8) -> Self {
CalculationResult { value, radix }
}
#[allow(dead_code)]
pub(crate) fn get_value(self) -> KalkValue {
self.value
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.value.to_string()
}
#[wasm_bindgen(js_name = toStringBig)]
pub fn to_string_big(&self) -> String {
self.value.to_string_big()
}
#[wasm_bindgen(js_name = toPrettyString)]
pub fn to_string_pretty(&self) -> String {
if self.radix == 10 {
self.value.to_string_pretty_radix(10)
} else {
format!(
"{}\n{}",
self.value.to_string_pretty_radix(10),
self.value.to_string_pretty_radix(self.radix),
)
}
}
#[wasm_bindgen(js_name = getValue)]
pub fn to_f64(&self) -> f64 {
self.value.to_f64()
}
#[wasm_bindgen(js_name = getImaginaryValue)]
pub fn imaginary_to_f64(&self) -> f64 {
self.value.imaginary_to_f64()
}
pub(crate) fn set_radix(&mut self, radix: u8) {
self.radix = radix;
}
#[wasm_bindgen(js_name = toScientificNotation)]
pub fn to_scientific_notation_js(
&self,
complex_number_type: ComplexNumberType,
) -> ScientificNotation {
self.value.to_scientific_notation(complex_number_type)
}
#[wasm_bindgen(js_name = estimate)]
pub fn estimate_js(&self) -> Option<String> {
self.value.estimate()
}
}

View File

@ -1,19 +1,21 @@
use crate::as_number_or_zero;
use crate::ast;
use crate::ast::Expr;
use crate::ast::Identifier;
use crate::ast::Stmt;
use crate::float;
use crate::interpreter;
use crate::kalk_num::KalkNum;
use crate::kalk_value::KalkValue;
use crate::lexer::TokenKind;
use crate::parser::CalcError;
pub fn derive_func(
context: &mut interpreter::Context,
name: &Identifier,
argument: KalkNum,
) -> Result<KalkNum, CalcError> {
argument: KalkValue,
) -> Result<KalkValue, CalcError> {
const H: f64 = 0.000001;
let unit = &argument.unit.to_string();
let unit = &argument.get_unit();
let argument_with_h = ast::build_literal_ast(&argument.clone().add_without_unit(H.into()));
let argument_without_h = ast::build_literal_ast(&argument.sub_without_unit(H.into()));
@ -34,7 +36,7 @@ pub fn integrate_with_unknown_variable(
a: &Expr,
b: &Expr,
expr: &Expr,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
let mut integration_variable: Option<&str> = None;
// integral(a, b, expr dx)
@ -66,7 +68,7 @@ pub fn integrate(
b: &Expr,
expr: &Expr,
integration_variable: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
Ok(simpsons_rule(context, a, b, expr, integration_variable)?.round_if_needed())
}
@ -77,8 +79,9 @@ fn simpsons_rule(
b_expr: &Expr,
expr: &Expr,
integration_variable: &str,
) -> Result<KalkNum, CalcError> {
let mut result = KalkNum::default();
) -> Result<KalkValue, CalcError> {
let mut result_real = float!(0);
let mut result_imaginary = float!(0);
let original_variable_value = context
.symbol_table
.get_and_remove_var(integration_variable);
@ -86,26 +89,27 @@ fn simpsons_rule(
const N: i32 = 900;
let a = interpreter::eval_expr(context, a_expr, "")?;
let b = interpreter::eval_expr(context, b_expr, "")?;
let h = (b.sub_without_unit(a.clone())).div_without_unit(KalkNum::from(N));
let h = (b.sub_without_unit(a.clone())).div_without_unit(KalkValue::from(N));
for i in 0..=N {
let variable_value = a
.clone()
.add_without_unit(KalkNum::from(i).mul_without_unit(h.clone()));
.add_without_unit(KalkValue::from(i).mul_without_unit(h.clone()));
context.symbol_table.set(Stmt::VarDecl(
Identifier::from_full_name(integration_variable),
Box::new(crate::ast::build_literal_ast(&variable_value)),
));
let factor = KalkNum::from(match i {
let factor = KalkValue::from(match i {
0 | N => 1,
_ if i % 3 == 0 => 2,
_ => 3,
});
// factor * f(x_n)
let mul = factor.mul_without_unit(interpreter::eval_expr(context, expr, "")?);
result.value += mul.value;
result.imaginary_value += mul.imaginary_value;
let (mul_real, mul_imaginary, _) =
as_number_or_zero!(factor.mul_without_unit(interpreter::eval_expr(context, expr, "")?));
result_real += mul_real;
result_imaginary += mul_imaginary;
}
if let Some(value) = original_variable_value {
@ -116,10 +120,13 @@ fn simpsons_rule(
.get_and_remove_var(integration_variable);
}
Ok(result.mul_without_unit(KalkNum::new_with_imaginary(
3f64 / 8f64 * h.value,
&h.unit,
3f64 / 8f64 * h.imaginary_value,
let result = KalkValue::Number(result_real, result_imaginary, String::new());
let (h_real, h_imaginary, h_unit) = as_number_or_zero!(h);
Ok(result.mul_without_unit(KalkValue::Number(
3f64 / 8f64 * h_real,
3f64 / 8f64 * h_imaginary,
h_unit,
)))
}
@ -128,8 +135,9 @@ mod tests {
use crate::ast;
use crate::calculus::Identifier;
use crate::calculus::Stmt;
use crate::float;
use crate::interpreter;
use crate::kalk_num::KalkNum;
use crate::kalk_value::KalkValue;
use crate::lexer::TokenKind::*;
use crate::symbol_table::SymbolTable;
use crate::test_helpers::*;
@ -210,7 +218,7 @@ mod tests {
let result = super::derive_func(
&mut context,
&Identifier::from_full_name("f'"),
KalkNum::new_with_imaginary(KalkNum::from(2f64).value, "", KalkNum::from(3f64).value),
KalkValue::Number(float!(2f64), float!(3f64), String::new()),
)
.unwrap();
assert!(cmp(result.to_f64(), -4.5f64) || cmp(result.to_f64(), -4.499999f64));
@ -255,10 +263,10 @@ mod tests {
let result = super::integrate(
&mut context,
&*literal(2f64),
&ast::build_literal_ast(&KalkNum::new_with_imaginary(
KalkNum::from(3f64).value,
"",
KalkNum::from(4f64).value,
&ast::build_literal_ast(&KalkValue::Number(
float!(3f64),
float!(4f64),
String::new(),
)),
&*binary(var("x"), Star, var("i")),
"x",

View File

@ -2,9 +2,9 @@
mod tests {
use std::{fs::File, io::Read, path::PathBuf};
use crate::kalk_num::KalkNum;
use crate::kalk_value::KalkValue;
fn eval_file(name: &str) -> KalkNum {
fn eval_file(name: &str) -> KalkValue {
let mut path = PathBuf::new();
path.push(env!("CARGO_MANIFEST_DIR"));
path.push("..");
@ -19,9 +19,17 @@ mod tests {
.unwrap();
let mut context = crate::parser::Context::new();
crate::parser::eval(&mut context, &file_content, 58)
#[cfg(feature = "rug")]
return crate::parser::eval(&mut context, &file_content, 58)
.unwrap()
.unwrap()
.get_value();
#[cfg(not(feature = "rug"))]
crate::parser::eval(&mut context, &file_content)
.unwrap()
.unwrap()
.get_value()
}
#[test]

View File

@ -1,12 +1,13 @@
use crate::ast::Identifier;
use crate::ast::{Expr, Stmt};
use crate::calculus;
use crate::kalk_num::KalkNum;
use crate::calculation_result::CalculationResult;
use crate::kalk_value::KalkValue;
use crate::lexer::TokenKind;
use crate::parser::CalcError;
use crate::parser::DECL_UNIT;
use crate::prelude;
use crate::symbol_table::SymbolTable;
use crate::{as_number_or_zero, calculus};
use crate::{float, prelude};
pub struct Context<'a> {
pub symbol_table: &'a mut SymbolTable,
@ -40,16 +41,19 @@ impl<'a> Context<'a> {
}
}
pub fn interpret(&mut self, statements: Vec<Stmt>) -> Result<Option<KalkNum>, CalcError> {
pub fn interpret(
&mut self,
statements: Vec<Stmt>,
) -> Result<Option<CalculationResult>, CalcError> {
for (i, stmt) in statements.iter().enumerate() {
let num = eval_stmt(self, stmt)?;
// Insert the last value into the `ans` variable.
self.symbol_table.set(if (&num.unit).len() > 0 {
self.symbol_table.set(if num.has_unit() {
Stmt::VarDecl(
Identifier::from_full_name("ans"),
Box::new(Expr::Unit(
num.unit.clone(),
num.get_unit().clone(),
Box::new(crate::ast::build_literal_ast(&num)),
)),
)
@ -62,7 +66,7 @@ impl<'a> Context<'a> {
if i == statements.len() - 1 {
if let Stmt::Expr(_) = stmt {
return Ok(Some(num));
return Ok(Some(CalculationResult::new(num, 10)));
}
}
}
@ -71,7 +75,7 @@ impl<'a> Context<'a> {
}
}
fn eval_stmt(context: &mut Context, stmt: &Stmt) -> Result<KalkNum, CalcError> {
fn eval_stmt(context: &mut Context, stmt: &Stmt) -> Result<KalkValue, CalcError> {
match stmt {
Stmt::VarDecl(_, _) => eval_var_decl_stmt(context, stmt),
Stmt::FnDecl(_, _, _) => eval_fn_decl_stmt(),
@ -80,20 +84,20 @@ fn eval_stmt(context: &mut Context, stmt: &Stmt) -> Result<KalkNum, CalcError> {
}
}
fn eval_var_decl_stmt(context: &mut Context, stmt: &Stmt) -> Result<KalkNum, CalcError> {
fn eval_var_decl_stmt(context: &mut Context, stmt: &Stmt) -> Result<KalkValue, CalcError> {
context.symbol_table.insert(stmt.clone());
Ok(KalkNum::from(1))
Ok(KalkValue::from(1))
}
fn eval_fn_decl_stmt() -> Result<KalkNum, CalcError> {
Ok(KalkNum::from(1)) // Nothing needs to happen here, since the parser will already have added the FnDecl's to the symbol table.
fn eval_fn_decl_stmt() -> Result<KalkValue, CalcError> {
Ok(KalkValue::from(1)) // Nothing needs to happen here, since the parser will already have added the FnDecl's to the symbol table.
}
fn eval_unit_decl_stmt() -> Result<KalkNum, CalcError> {
Ok(KalkNum::from(1))
fn eval_unit_decl_stmt() -> Result<KalkValue, CalcError> {
Ok(KalkValue::from(1))
}
fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result<KalkNum, CalcError> {
fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result<KalkValue, CalcError> {
eval_expr(context, &expr, "")
}
@ -101,7 +105,7 @@ pub(crate) fn eval_expr(
context: &mut Context,
expr: &Expr,
unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
#[cfg(not(target_arch = "wasm32"))]
if let (Ok(elapsed), Some(timeout)) = (context.start_time.elapsed(), context.timeout) {
if elapsed.as_millis() >= timeout {
@ -129,12 +133,12 @@ fn eval_binary_expr(
op: &TokenKind,
right_expr: &Expr,
unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
if let TokenKind::ToKeyword = op {
// TODO: When the unit conversion function takes a Float instead of Expr,
// move this to the match statement further down.
if let Expr::Var(right_unit) = right_expr {
let left_unit = eval_expr(context, left_expr, "")?.unit;
let left_unit = eval_expr(context, left_expr, "")?.get_unit();
return convert_unit(context, left_expr, &left_unit, &right_unit.full_name);
// TODO: Avoid evaluating this twice.
}
@ -149,7 +153,7 @@ fn eval_binary_expr(
}
}
let mut result = match op {
let result = match op {
TokenKind::Plus => left.add(context, right),
TokenKind::Minus => left.sub(context, right),
TokenKind::Star => left.mul(context, right),
@ -162,11 +166,13 @@ fn eval_binary_expr(
TokenKind::LessThan => left.less_than(context, right),
TokenKind::GreaterOrEquals => left.greater_or_equals(context, right),
TokenKind::LessOrEquals => left.less_or_equals(context, right),
_ => KalkNum::from(1),
_ => KalkValue::from(1),
};
if unit.len() > 0 {
result.unit = unit.to_string();
if let KalkValue::Number(real, imaginary, _) = result {
return Ok(KalkValue::Number(real, imaginary, unit.to_string()));
}
};
Ok(result)
@ -177,12 +183,12 @@ fn eval_unary_expr(
op: &TokenKind,
expr: &Expr,
unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
let num = eval_expr(context, &expr, unit)?;
match op {
TokenKind::Minus => Ok(num.mul(context, KalkNum::from(-1f64))),
TokenKind::Percent => Ok(num.mul(context, KalkNum::from(0.01f64))),
TokenKind::Minus => Ok(num.mul(context, KalkValue::from(-1f64))),
TokenKind::Percent => Ok(num.mul(context, KalkValue::from(0.01f64))),
TokenKind::Exclamation => Ok(prelude::special_funcs::factorial(num)),
_ => Err(CalcError::InvalidOperator),
}
@ -192,7 +198,7 @@ fn eval_unit_expr(
context: &mut Context,
identifier: &str,
expr: &Expr,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
let angle_unit = &context.angle_unit.clone();
if (identifier == "rad" || identifier == "deg") && angle_unit != identifier {
return convert_unit(context, expr, identifier, angle_unit);
@ -206,7 +212,7 @@ pub fn convert_unit(
expr: &Expr,
from_unit: &str,
to_unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
if let Some(Stmt::UnitDecl(_, _, unit_def)) =
context.symbol_table.get_unit(to_unit, from_unit).cloned()
{
@ -215,12 +221,8 @@ pub fn convert_unit(
Box::new(expr.clone()),
));
let num = eval_expr(context, &unit_def, "")?;
Ok(KalkNum::new_with_imaginary(
num.value,
to_unit.into(),
num.imaginary_value,
))
let (real, imaginary, _) = as_number_or_zero!(eval_expr(context, &unit_def, "")?);
Ok(KalkValue::Number(real, imaginary, to_unit.into()))
} else {
Err(CalcError::InvalidUnit)
}
@ -230,7 +232,7 @@ fn eval_var_expr(
context: &mut Context,
identifier: &Identifier,
unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
// If there is a constant with this name, return a literal expression with its value
if let Some(value) = prelude::CONSTANTS.get(identifier.full_name.as_ref() as &str) {
return eval_expr(context, &Expr::Literal(*value), unit);
@ -238,7 +240,7 @@ fn eval_var_expr(
if identifier.full_name == "n" {
if let Some(value) = context.sum_n_value {
return Ok(KalkNum::from(value));
return Ok(KalkValue::from(value));
}
}
@ -254,17 +256,33 @@ fn eval_var_expr(
}
#[allow(unused_variables)]
fn eval_literal_expr(context: &mut Context, value: f64, unit: &str) -> Result<KalkNum, CalcError> {
let mut num: KalkNum = value.into();
num.unit = unit.into();
#[cfg(feature = "rug")]
fn eval_literal_expr(
context: &mut Context,
value: f64,
unit: &str,
) -> Result<KalkValue, CalcError> {
let mut float = float!(value);
float.set_prec(context.precision);
#[cfg(feature = "rug")]
num.value.set_prec(context.precision);
Ok(num)
Ok(KalkValue::Number(float, float!(0), unit.to_string()))
}
fn eval_group_expr(context: &mut Context, expr: &Expr, unit: &str) -> Result<KalkNum, CalcError> {
#[allow(unused_variables)]
#[cfg(not(feature = "rug"))]
fn eval_literal_expr(
context: &mut Context,
value: f64,
unit: &str,
) -> Result<KalkValue, CalcError> {
Ok(KalkValue::Number(
float!(value),
float!(0),
unit.to_string(),
))
}
fn eval_group_expr(context: &mut Context, expr: &Expr, unit: &str) -> Result<KalkValue, CalcError> {
eval_expr(context, expr, unit)
}
@ -273,7 +291,7 @@ pub(crate) fn eval_fn_call_expr(
identifier: &Identifier,
expressions: &[Expr],
unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
// Prelude
let prelude_func = match expressions.len() {
1 => {
@ -327,27 +345,26 @@ pub(crate) fn eval_fn_call_expr(
_ => unreachable!(),
};
let mut sum = if sum_else_prod {
KalkNum::default()
KalkValue::from(0f64)
} else {
KalkNum::from(1f64)
KalkValue::from(1f64)
};
for n in start..=end {
context.sum_n_value = Some(n);
let eval = eval_expr(context, &expressions[2], "")?;
if sum_else_prod {
sum.value += eval.value;
sum.imaginary_value += eval.imaginary_value;
sum = sum.add(context, eval);
} else {
sum.value *= eval.value;
sum.imaginary_value *= eval.imaginary_value;
sum = sum.mul(context, eval);
}
}
context.sum_n_value = None;
sum.unit = unit.into();
let (sum_real, sum_imaginary, _) = as_number_or_zero!(sum);
return Ok(sum);
// Set the unit as well
return Ok(KalkValue::Number(sum_real, sum_imaginary, unit.to_string()));
}
"integrate" => {
return match expressions.len() {
@ -447,9 +464,9 @@ fn eval_piecewise(
context: &mut Context,
pieces: &Vec<crate::ast::ConditionalPiece>,
unit: &str,
) -> Result<KalkNum, CalcError> {
) -> Result<KalkValue, CalcError> {
for piece in pieces {
if let Some(condition_is_true) = eval_expr(context, &piece.condition, unit)?.boolean_value {
if let KalkValue::Boolean(condition_is_true) = eval_expr(context, &piece.condition, unit)? {
if condition_is_true {
return Ok(eval_expr(context, &piece.expr, unit)?);
}
@ -491,7 +508,7 @@ mod tests {
);
}
fn interpret_with_unit(stmt: Stmt) -> Result<Option<KalkNum>, CalcError> {
fn interpret_with_unit(stmt: Stmt) -> Result<Option<CalculationResult>, CalcError> {
let mut symbol_table = SymbolTable::new();
symbol_table
.insert(DEG_RAD_UNIT.clone())
@ -500,9 +517,9 @@ mod tests {
context(&mut symbol_table, "rad").interpret(vec![stmt])
}
fn interpret(stmt: Stmt) -> Result<Option<KalkNum>, CalcError> {
fn interpret(stmt: Stmt) -> Result<Option<KalkValue>, CalcError> {
if let Some(result) = interpret_with_unit(stmt)? {
Ok(Some(result))
Ok(Some(result.get_value()))
} else {
Ok(None)
}
@ -518,10 +535,18 @@ mod tests {
Context::new(symbol_table, angle_unit, None)
}
fn cmp(x: KalkNum, y: f64) -> bool {
fn cmp(x: KalkValue, y: f64) -> bool {
(x.to_f64() - y).abs() < 0.0001
}
fn bool(x: &KalkValue) -> bool {
if let KalkValue::Boolean(boolean_value) = x {
*boolean_value
} else {
false
}
}
#[test]
fn test_literal() {
let stmt = Stmt::Expr(literal(1f64));
@ -550,40 +575,28 @@ mod tests {
assert_eq!(interpret(pow).unwrap().unwrap().to_f64(), 8f64);
let result = interpret(equals).unwrap().unwrap();
assert_eq!(
(result.to_f64(), result.boolean_value.unwrap()),
(0f64, false)
);
assert_eq!(bool(&result), false);
assert!(result.to_f64().is_nan());
let result = interpret(not_equals).unwrap().unwrap();
assert_eq!(
(result.to_f64(), result.boolean_value.unwrap()),
(0f64, true)
);
assert_eq!(bool(&result), true);
assert!(result.to_f64().is_nan());
let result = interpret(greater_than).unwrap().unwrap();
assert_eq!(
(result.to_f64(), result.boolean_value.unwrap()),
(0f64, false)
);
assert_eq!(bool(&result), false);
assert!(result.to_f64().is_nan());
let result = interpret(less_than).unwrap().unwrap();
assert_eq!(
(result.to_f64(), result.boolean_value.unwrap()),
(0f64, true)
);
assert_eq!(bool(&result), true);
assert!(result.to_f64().is_nan());
let result = interpret(greater_or_equals).unwrap().unwrap();
assert_eq!(
(result.to_f64(), result.boolean_value.unwrap()),
(0f64, false)
);
assert_eq!(bool(&result), false);
assert!(result.to_f64().is_nan());
let result = interpret(less_or_equals).unwrap().unwrap();
assert_eq!(
(result.to_f64(), result.boolean_value.unwrap()),
(0f64, true)
);
assert_eq!(bool(&result), true);
assert!(result.to_f64().is_nan());
}
#[test]
@ -630,11 +643,16 @@ mod tests {
rad_context
.interpret(vec![implicit.clone()])
.unwrap()
.unwrap(),
.unwrap()
.get_value(),
0.84147098
));
assert!(cmp(
deg_context.interpret(vec![implicit]).unwrap().unwrap(),
deg_context
.interpret(vec![implicit])
.unwrap()
.unwrap()
.get_value(),
0.01745240
));
}

View File

@ -1,177 +0,0 @@
use crate::kalk_num::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(PartialEq, Debug, Clone, Default)]
pub struct KalkNum {
pub(crate) value: f64,
pub(crate) unit: String,
pub(crate) imaginary_value: f64,
pub(crate) boolean_value: Option<bool>,
pub(crate) other_radix: Option<u8>,
}
#[wasm_bindgen]
impl KalkNum {
pub fn new(value: f64, unit: &str) -> Self {
Self {
value,
unit: unit.to_string(),
imaginary_value: 0f64,
boolean_value: None,
other_radix: None,
}
}
pub fn new_with_imaginary(value: f64, unit: &str, imaginary_value: f64) -> Self {
Self {
value,
unit: unit.to_string(),
imaginary_value: if imaginary_value == -0f64 {
0f64
} else {
imaginary_value
},
boolean_value: None,
other_radix: None,
}
}
pub fn from_imaginary(value: f64) -> Self {
Self {
value: 0f64,
unit: String::new(),
imaginary_value: value,
boolean_value: None,
other_radix: None,
}
}
pub fn from_bool(value: bool) -> Self {
Self {
value: 0f64,
unit: String::new(),
imaginary_value: 0f64,
boolean_value: Some(value),
other_radix: None,
}
}
#[wasm_bindgen(js_name = getValue)]
pub fn to_f64(&self) -> f64 {
self.value
}
#[wasm_bindgen(js_name = getImaginaryValue)]
pub fn imaginary_to_f64(&self) -> f64 {
self.imaginary_value
}
pub fn to_i32(&self) -> i32 {
self.value as i32
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string_js(&self) -> String {
self.to_string()
}
#[wasm_bindgen(js_name = toPrettyString)]
pub fn to_string_pretty_js(&self) -> String {
self.to_string_pretty()
}
#[wasm_bindgen(js_name = toStringBig)]
pub fn to_string_big_js(&self) -> String {
self.to_string_big()
}
#[wasm_bindgen(js_name = isTooBig)]
pub fn is_too_big_js(&self) -> bool {
self.is_too_big()
}
#[wasm_bindgen(js_name = toStringWithUnit)]
pub fn to_string_with_unit_js(&self) -> String {
self.to_string_with_unit()
}
#[wasm_bindgen(js_name = hasUnit)]
pub fn has_unit_js(&self) -> bool {
self.has_unit()
}
#[wasm_bindgen(js_name = getUnit)]
pub fn get_unit(&self) -> String {
self.unit.clone()
}
#[wasm_bindgen(js_name = toScientificNotation)]
pub fn to_scientific_notation_js(
&self,
complex_number_type: ComplexNumberType,
) -> ScientificNotation {
self.to_scientific_notation(complex_number_type)
}
#[wasm_bindgen(js_name = estimate)]
pub fn estimate_js(&self) -> Option<String> {
self.estimate()
}
pub(crate) fn pow(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
self.pow_without_unit(right)
}
pub(crate) fn pow_without_unit(self, rhs: KalkNum) -> KalkNum {
if self.has_imaginary() || rhs.has_imaginary() || (self.value < 0f64 && rhs.value < 1f64) {
let a = self.value.clone();
let b = self.imaginary_value.clone();
let c = rhs.value;
let d = rhs.imaginary_value;
let arg = crate::prelude::funcs::arg(self).value;
let raised = a.clone() * a + b.clone() * b;
let exp = raised.clone().powf(c.clone() / 2f64) * (-d.clone() * arg.clone()).exp();
let polar = c * arg + d / 2f64 * raised.ln();
KalkNum::new_with_imaginary(
polar.clone().cos() * exp.clone(),
&rhs.unit,
polar.sin() * exp,
)
} else {
KalkNum::new(self.value.powf(rhs.value), &rhs.unit)
}
}
}
impl From<f64> for KalkNum {
fn from(x: f64) -> Self {
KalkNum::new(x, "")
}
}
impl From<f32> for KalkNum {
fn from(x: f32) -> Self {
KalkNum::new(x as f64, "")
}
}
impl From<i128> for KalkNum {
fn from(x: i128) -> Self {
KalkNum::new(x as f64, "")
}
}
impl From<i64> for KalkNum {
fn from(x: i64) -> Self {
KalkNum::new(x as f64, "")
}
}
impl From<i32> for KalkNum {
fn from(x: i32) -> Self {
KalkNum::new(x as f64, "")
}
}

View File

@ -1,136 +0,0 @@
use crate::kalk_num::*;
impl Default for KalkNum {
fn default() -> Self {
KalkNum::new(Float::with_val(63, 0), "")
}
}
#[derive(PartialEq, Debug, Clone)]
pub struct KalkNum {
pub(crate) value: Float,
pub(crate) unit: String,
pub(crate) imaginary_value: Float,
pub(crate) boolean_value: Option<bool>,
pub(crate) other_radix: Option<u8>,
}
impl KalkNum {
pub fn new(value: Float, unit: &str) -> Self {
let precision = value.prec();
Self {
value,
unit: unit.to_string(),
imaginary_value: Float::with_val(precision, 0),
boolean_value: None,
other_radix: None,
}
}
pub fn new_with_imaginary(value: Float, unit: &str, imaginary_value: Float) -> Self {
Self {
value,
unit: unit.to_string(),
imaginary_value: if imaginary_value == -0f64 {
imaginary_value.abs()
} else {
imaginary_value
},
boolean_value: None,
other_radix: None,
}
}
pub fn from_imaginary(value: Float) -> Self {
Self {
value: Float::with_val(value.prec(), 0),
unit: String::new(),
imaginary_value: value,
boolean_value: None,
other_radix: None,
}
}
pub fn from_bool(value: bool) -> Self {
Self {
value: Float::with_val(63, 0),
unit: String::new(),
imaginary_value: Float::with_val(63, 0),
boolean_value: Some(value),
other_radix: None,
}
}
pub fn to_f64(&self) -> f64 {
self.value.to_f64_round(rug::float::Round::Nearest)
}
pub fn imaginary_to_f64(&self) -> f64 {
self.imaginary_value
.to_f64_round(rug::float::Round::Nearest)
}
pub fn to_i32(&self) -> i32 {
self.value.to_i32_saturating().unwrap_or(0i32)
}
pub fn get_unit(&self) -> &str {
&self.unit
}
pub(crate) fn pow(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
self.pow_without_unit(right)
}
pub(crate) fn pow_without_unit(self, rhs: KalkNum) -> KalkNum {
if self.has_imaginary() || rhs.has_imaginary() || (self.value < 0f64 && rhs.value < 1f64) {
let a = self.value.clone();
let b = self.imaginary_value.clone();
let c = rhs.value;
let d = rhs.imaginary_value;
let arg = crate::prelude::funcs::arg(self).value;
let raised = a.clone() * a + b.clone() * b;
let exp = raised.clone().pow(c.clone() / 2f64) * (-d.clone() * arg.clone()).exp();
let polar = c * arg + d / 2f64 * raised.ln();
KalkNum::new_with_imaginary(
polar.clone().cos() * exp.clone(),
&rhs.unit,
polar.sin() * exp,
)
} else {
KalkNum::new(self.value.pow(rhs.value), &rhs.unit)
}
}
}
impl From<f64> for KalkNum {
fn from(x: f64) -> Self {
KalkNum::new(Float::with_val(63, x), "")
}
}
impl From<f32> for KalkNum {
fn from(x: f32) -> Self {
KalkNum::new(Float::with_val(63, x), "")
}
}
impl From<i128> for KalkNum {
fn from(x: i128) -> Self {
KalkNum::new(Float::with_val(63, x), "")
}
}
impl From<i64> for KalkNum {
fn from(x: i64) -> Self {
KalkNum::new(Float::with_val(63, x), "")
}
}
impl From<i32> for KalkNum {
fn from(x: i32) -> Self {
KalkNum::new(Float::with_val(63, x), "")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
use crate::kalk_value::*;
impl KalkValue {
pub fn to_f64(&self) -> f64 {
if let KalkValue::Number(real, _, _) = self {
*real
} else {
f64::NAN
}
}
pub fn imaginary_to_f64(&self) -> f64 {
if let KalkValue::Number(_, imaginary, _) = self {
*imaginary
} else {
f64::NAN
}
}
pub fn values(self) -> (f64, f64) {
if let KalkValue::Number(real, imaginary, _) = self {
(real, imaginary)
} else {
(0f64, 0f64)
}
}
}

View File

@ -0,0 +1,166 @@
use crate::float;
use super::{ComplexNumberType, KalkValue, CONSTANTS};
pub(super) fn estimate(
input: &KalkValue,
complex_number_type: ComplexNumberType,
) -> Option<String> {
let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input {
(real, imaginary, unit)
} else {
return None;
};
let (value, value_string) = match complex_number_type {
ComplexNumberType::Real => (real, input.to_string_real(10)),
ComplexNumberType::Imaginary => (imaginary, input.to_string_imaginary(10, true)),
};
let fract = value.clone().fract().abs();
let integer = value.clone().trunc();
#[cfg(feature = "rug")]
let fract_as_string = fract.to_f64().to_string();
#[cfg(not(feature = "rug"))]
let fract_as_string = fract.to_string();
// If it's an integer, there's nothing that would be done to it.
if fract == 0f64 {
return None;
}
// Eg. 0.5 to 1/2
let as_abs_string = value_string.trim_start_matches("-").to_string();
let sign = if value < &0f64 { "-" } else { "" };
if as_abs_string.starts_with("0.5") {
if as_abs_string.len() == 3 || (as_abs_string.len() > 6 && &as_abs_string[3..5] == "00") {
return Some(format!("{}1/2", sign));
}
}
// Eg. 1.33333333 to 1 + 1/3
if fract_as_string.len() >= 7 {
let first_five_decimals = &fract_as_string[2..7];
if first_five_decimals == "33333" || first_five_decimals == "66666" {
let fraction = match first_five_decimals.as_ref() {
"33333" => "1/3",
"66666" => "2/3",
_ => "?",
};
if integer == 0f64 {
return Some(format!("{}{}", sign, fraction));
} else {
let explicit_sign = if sign == "" { "+" } else { "-" };
return Some(format!(
"{} {} {}",
trim_zeroes(&integer.to_string()),
explicit_sign,
fraction
));
}
}
}
// Match with common numbers, eg. π, 2π/3, √2
if as_abs_string.len() >= 8 {
if let Some(constant) = CONSTANTS.get(&as_abs_string[0..8]) {
return Some(format!("{}{}", sign, constant.to_string()));
}
}
// If the value squared (and rounded) is an integer,
// eg. x² is an integer,
// then it can be expressed as sqrt(x²).
// Ignore it if the square root of the result is an integer.
if fract != 0f64 {
let squared = KalkValue::Number(value.clone() * value, float!(0), String::new())
.round_if_needed()
.values()
.0;
if squared.clone().sqrt().fract() != 0f64 && squared.clone().fract() == 0f64 {
return Some(format!("{}", squared.to_string()));
}
}
// If nothing above was relevant, simply round it off a bit, eg. from 0.99999 to 1
let rounded = match complex_number_type {
ComplexNumberType::Real => round(input, complex_number_type)?.values().0,
ComplexNumberType::Imaginary => round(input, complex_number_type)?.values().1,
};
let rounded_str = rounded.to_string();
Some(trim_zeroes(if rounded_str == "-0" {
"0"
} else {
&rounded_str
}))
}
pub(super) fn round(
input: &KalkValue,
complex_number_type: ComplexNumberType,
) -> Option<KalkValue> {
let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input {
(real, imaginary, unit)
} else {
return None;
};
let value = match complex_number_type {
ComplexNumberType::Real => real,
ComplexNumberType::Imaginary => imaginary,
};
let sign = if *value < 0f64 { -1f64 } else { 1f64 };
let fract = value.clone().abs().fract();
let integer = value.clone().abs().trunc();
// If it's zero something, don't do the rounding as aggressively.
let (limit_floor, limit_ceil) = if integer == 0f64 {
(-8f64, -5f64)
} else {
(-4f64, -6f64)
};
if fract.clone().log10() < limit_floor {
// If eg. 0.00xxx
let new_value = integer * sign;
let new_num = match complex_number_type {
ComplexNumberType::Real => {
KalkValue::Number(new_value, imaginary.clone(), input.get_unit())
}
ComplexNumberType::Imaginary => {
KalkValue::Number(real.clone(), new_value, input.get_unit())
}
};
Some(new_num)
} else if (1f64 - fract.clone()).log10() < limit_ceil {
// If eg. 0.999
// .abs() this before ceiling to make sure it rounds correctly. The sign is re-added afterwards.
let new_value = value.clone().abs().ceil() * sign;
let new_num = match complex_number_type {
ComplexNumberType::Real => {
KalkValue::Number(new_value, imaginary.clone(), input.get_unit())
}
ComplexNumberType::Imaginary => {
KalkValue::Number(real.clone(), new_value, input.get_unit())
}
};
Some(new_num)
} else {
None
}
}
pub(super) fn trim_zeroes(input: &str) -> String {
if input.contains(".") {
input
.trim_end_matches("0")
.trim_end_matches(".")
.to_string()
} else {
input.into()
}
}

View File

@ -0,0 +1,27 @@
use crate::kalk_value::*;
impl KalkValue {
pub fn to_f64(&self) -> f64 {
if let KalkValue::Number(real, _, _) = self {
real.to_f64_round(rug::float::Round::Nearest)
} else {
f64::NAN
}
}
pub fn imaginary_to_f64(&self) -> f64 {
if let KalkValue::Number(_, imaginary, _) = self {
imaginary.to_f64_round(rug::float::Round::Nearest)
} else {
f64::NAN
}
}
pub fn values(self) -> (Float, Float) {
if let KalkValue::Number(real, imaginary, _) = self {
(real, imaginary)
} else {
(Float::with_val(63, 0), Float::with_val(63, 0))
}
}
}

View File

@ -1,9 +1,10 @@
pub mod ast;
pub mod calculation_result;
mod calculus;
mod integration_testing;
mod interpreter;
mod inverter;
pub mod kalk_num;
pub mod kalk_value;
mod lexer;
pub mod parser;
mod prelude;

View File

@ -1,5 +1,5 @@
use crate::ast::Identifier;
use crate::kalk_num::KalkNum;
use crate::calculation_result::CalculationResult;
use crate::{
ast::{Expr, Stmt},
interpreter, inverter,
@ -79,7 +79,7 @@ impl Context {
#[wasm_bindgen(js_name = evaluate)]
#[cfg(not(feature = "rug"))]
pub fn js_eval(&mut self, input: &str) -> Result<Option<KalkNum>, JsValue> {
pub fn js_eval(&mut self, input: &str) -> Result<Option<CalculationResult>, JsValue> {
let result = eval(self, input);
match result {
@ -158,7 +158,7 @@ pub fn eval(
context: &mut Context,
input: &str,
#[cfg(feature = "rug")] precision: u32,
) -> Result<Option<KalkNum>, CalcError> {
) -> Result<Option<CalculationResult>, CalcError> {
// Variable and function declaration parsers will set this to false
// if the equal sign is for one of those instead.
// It also should not contain an iverson bracket, since equal signs in there
@ -180,7 +180,7 @@ pub fn eval(
);
let result = interpreter.interpret(statements);
if let Ok(Some(mut num)) = result {
num.other_radix = context.other_radix;
num.set_radix(context.other_radix.unwrap_or(10));
Ok(Some(num))
} else {
result

View File

@ -1,4 +1,4 @@
use crate::kalk_num::KalkNum;
use crate::kalk_value::KalkValue;
use lazy_static::lazy_static;
use std::collections::HashMap;
use FuncType::*;
@ -77,7 +77,7 @@ lazy_static! {
m.insert("abs", (UnaryFuncInfo(abs, Other), ""));
m.insert("cbrt", (UnaryFuncInfo(cbrt, Other), ""));
m.insert("ceil", (UnaryFuncInfo(ceil, Other), ""));
m.insert("iverson", (UnaryFuncInfo(inverson, Other), ""));
m.insert("iverson", (UnaryFuncInfo(iverson, Other), ""));
m.insert("exp", (UnaryFuncInfo(exp, Other), ""));
m.insert("floor", (UnaryFuncInfo(floor, Other), ""));
m.insert("frac", (UnaryFuncInfo(frac, Other), ""));
@ -121,13 +121,17 @@ enum FuncType {
Other,
}
// Unary functions
pub struct UnaryFuncInfo(fn(KalkNum) -> KalkNum, FuncType);
pub struct UnaryFuncInfo(fn(KalkValue) -> KalkValue, FuncType);
pub struct BinaryFuncInfo(fn(KalkNum, KalkNum) -> KalkNum, FuncType);
pub struct BinaryFuncInfo(fn(KalkValue, KalkValue) -> KalkValue, FuncType);
impl UnaryFuncInfo {
fn call(&self, context: &mut interpreter::Context, x: KalkNum, angle_unit: &str) -> KalkNum {
fn call(
&self,
context: &mut interpreter::Context,
x: KalkValue,
angle_unit: &str,
) -> KalkValue {
let func = self.0;
match self.1 {
FuncType::Trig => func(from_angle_unit(context, x, angle_unit)),
@ -141,10 +145,10 @@ impl BinaryFuncInfo {
fn call(
&self,
context: &mut interpreter::Context,
x: KalkNum,
y: KalkNum,
x: KalkValue,
y: KalkValue,
angle_unit: &str,
) -> KalkNum {
) -> KalkValue {
let func = self.0;
match self.1 {
FuncType::Trig => func(
@ -177,9 +181,9 @@ pub fn is_constant(identifier: &str) -> bool {
pub fn call_unary_func(
context: &mut interpreter::Context,
name: &str,
x: KalkNum,
x: KalkValue,
angle_unit: &str,
) -> Option<(KalkNum, String)> {
) -> Option<(KalkValue, String)> {
if let Some((func_info, func_unit)) = UNARY_FUNCS.get(name) {
Some((
func_info.call(context, x, &angle_unit),
@ -193,10 +197,10 @@ pub fn call_unary_func(
pub fn call_binary_func(
context: &mut interpreter::Context,
name: &str,
x: KalkNum,
y: KalkNum,
x: KalkValue,
y: KalkValue,
angle_unit: &str,
) -> Option<(KalkNum, String)> {
) -> Option<(KalkValue, String)> {
if let Some((func_info, func_unit)) = BINARY_FUNCS.get(name) {
Some((
func_info.call(context, x, y, angle_unit),
@ -207,7 +211,7 @@ pub fn call_binary_func(
}
}
fn to_angle_unit(context: &mut interpreter::Context, x: KalkNum, angle_unit: &str) -> KalkNum {
fn to_angle_unit(context: &mut interpreter::Context, x: KalkValue, angle_unit: &str) -> KalkValue {
match angle_unit {
"rad" => x,
_ => interpreter::convert_unit(context, &Expr::Literal(x.to_f64()), "rad", angle_unit)
@ -215,7 +219,11 @@ fn to_angle_unit(context: &mut interpreter::Context, x: KalkNum, angle_unit: &st
}
}
fn from_angle_unit(context: &mut interpreter::Context, x: KalkNum, angle_unit: &str) -> KalkNum {
fn from_angle_unit(
context: &mut interpreter::Context,
x: KalkValue,
angle_unit: &str,
) -> KalkValue {
match angle_unit {
"rad" => x,
_ => interpreter::convert_unit(context, &Expr::Literal(x.to_f64()), angle_unit, "rad")
@ -229,305 +237,333 @@ pub mod funcs {
use super::special_funcs::factorial;
#[cfg(feature = "rug")]
pub use super::with_rug::funcs::*;
use crate::kalk_num::KalkNum;
use crate::{as_number_or_return, float, kalk_value::KalkValue};
pub fn abs(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
pub fn abs(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
if imaginary != 0f64 {
// |z| = sqrt(a² + b²)
let a = x.value.clone() * x.value;
let b = x.imaginary_value.clone() * x.imaginary_value;
let a = real.clone() * real;
let b = imaginary.clone() * imaginary;
sqrt(KalkNum::new(a + b, &x.unit))
sqrt(KalkValue::Number(a + b, float!(0), unit))
} else {
KalkNum::new(x.value.abs(), &x.unit)
KalkValue::Number(real.abs(), float!(0), unit)
}
}
pub fn acos(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value > 1f64 || x.value < -1f64 {
pub fn acos(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real > 1f64 || real < -1f64 {
// -i * ln(i * sqrt(1 - z²) + z)
let root =
sqrt(KalkNum::from(1f64).sub_without_unit(x.clone().mul_without_unit(x.clone())));
sqrt(KalkValue::from(1f64).sub_without_unit(x.clone().mul_without_unit(x.clone())));
let iroot = multiply_with_i(root.clone());
let ln = ln(iroot.add_without_unit(x));
let (ln_real, ln_imaginary, ln_unit) =
as_number_or_return!(ln(iroot.add_without_unit(x)));
// -iz = -i(a + bi) = b - ai
KalkNum::new_with_imaginary(ln.imaginary_value, &ln.unit, -ln.value)
KalkValue::Number(ln_imaginary, -ln_real, ln_unit)
} else {
KalkNum::new(x.value.acos(), &x.unit)
KalkValue::Number(real.acos(), float!(0), unit)
}
}
pub fn acosh(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value < 1f64 {
let sqrt1 = sqrt(KalkNum::new_with_imaginary(
x.value.clone() + 1f64,
&x.unit,
x.imaginary_value.clone(),
pub fn acosh(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real < 1f64 {
let sqrt1 = sqrt(KalkValue::Number(
real.clone() + 1f64,
imaginary.clone(),
unit.clone(),
));
let sqrt2 = sqrt(KalkNum::new_with_imaginary(
x.value.clone() - 1f64,
&x.unit,
x.imaginary_value.clone(),
let sqrt2 = sqrt(KalkValue::Number(
real.clone() - 1f64,
imaginary.clone(),
unit,
));
ln(x.add_without_unit(sqrt1.mul_without_unit(sqrt2)))
} else {
KalkNum::new(x.value.acosh(), &x.unit)
KalkValue::Number(real.acosh(), float!(0), unit)
}
}
pub fn acot(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
pub fn acot(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 {
// atan(1/z)
atan(KalkNum::from(1f64).div_without_unit(x))
atan(KalkValue::from(1f64).div_without_unit(x))
} else {
KalkNum::new((1f64 / x.value).atan(), &x.unit)
KalkValue::Number((1f64 / real).atan(), float!(0), unit)
}
}
pub fn acoth(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value <= 1f64 || x.value >= -1f64 {
pub fn acoth(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real <= 1f64 || real >= -1f64 {
// 1 / z
let inv_x = KalkNum::from(1f64).div_without_unit(x);
let ln1 = ln(KalkNum::new_with_imaginary(
1f64 + inv_x.value.clone(),
&inv_x.unit,
inv_x.imaginary_value.clone(),
));
let ln2 = ln(KalkNum::new_with_imaginary(
1f64 - inv_x.value,
&inv_x.unit,
-inv_x.imaginary_value,
let (inv_real, inv_imaginary, inv_unit) =
as_number_or_return!(KalkValue::from(1f64).div_without_unit(x));
let ln1 = ln(KalkValue::Number(
1f64 + inv_real.clone(),
inv_imaginary.clone(),
inv_unit.clone(),
));
let ln2 = ln(KalkValue::Number(1f64 - inv_real, -inv_imaginary, inv_unit));
ln1.sub_without_unit(ln2)
.div_without_unit(KalkNum::from(2f64))
.div_without_unit(KalkValue::from(2f64))
} else {
KalkNum::new((1f64 / x.value).atanh(), &x.unit)
KalkValue::Number((1f64 / real).atanh(), float!(0), unit)
}
}
pub fn acsc(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value < 1f64 || x.value > -1f64 {
pub fn acsc(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real < 1f64 || real > -1f64 {
// asin(1/z)
asin(KalkNum::from(1f64).div_without_unit(x))
asin(KalkValue::from(1f64).div_without_unit(x))
} else {
KalkNum::new((1f64 / x.value).asin(), &x.unit)
KalkValue::Number((1f64 / real).asin(), float!(0), unit)
}
}
pub fn acsch(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value == 0f64 {
let inv_x2 =
KalkNum::from(1f64).div_without_unit(x.clone().mul_without_unit(x.clone()));
let sqrt = sqrt(KalkNum::new_with_imaginary(
1f64 + inv_x2.value,
&inv_x2.unit,
inv_x2.imaginary_value,
pub fn acsch(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real == 0f64 {
let (inv_x2_real, inv_x2_imaginary, inv_x2_unit) = as_number_or_return!(
KalkValue::from(1f64).div_without_unit(x.clone().mul_without_unit(x.clone()))
);
let sqrt = sqrt(KalkValue::Number(
1f64 + inv_x2_real,
inv_x2_imaginary,
inv_x2_unit,
));
let inv_x = KalkNum::from(1f64).div_without_unit(x.clone());
let inv_x = KalkValue::from(1f64).div_without_unit(x.clone());
ln(sqrt.add_without_unit(inv_x))
} else {
KalkNum::new((1f64 / x.value).asinh(), &x.unit)
KalkValue::Number((1f64 / real).asinh(), float!(0), unit)
}
}
pub fn asec(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value < 1f64 || x.value > -1f64 {
pub fn asec(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real < 1f64 || real > -1f64 {
// acos(1/z)
acos(KalkNum::from(1f64).div_without_unit(x))
acos(KalkValue::from(1f64).div_without_unit(x))
} else {
KalkNum::new((1f64 / x.value).acos(), &x.unit)
KalkValue::Number((1f64 / real).acos(), float!(0), unit)
}
}
pub fn asech(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value <= 0f64 || x.value > 1f64 {
pub fn asech(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real <= 0f64 || real > 1f64 {
// 1/z
let inv_x = KalkNum::from(1f64).div_without_unit(x.clone());
let inv = KalkValue::from(1f64).div_without_unit(x.clone());
let (inv_real, inv_imaginary, inv_unit) = as_number_or_return!(inv.clone());
// sqrt(1/z - 1)
let sqrt1 = sqrt(KalkNum::new_with_imaginary(
inv_x.value.clone() - 1f64,
&inv_x.unit,
inv_x.imaginary_value.clone(),
let sqrt1 = sqrt(KalkValue::Number(
inv_real.clone() - 1f64,
inv_imaginary.clone(),
inv_unit.clone(),
));
// sqrt(1/z + 1)
let sqrt2 = sqrt(KalkNum::new_with_imaginary(
inv_x.value.clone() + 1f64,
&inv_x.unit,
inv_x.imaginary_value.clone(),
let sqrt2 = sqrt(KalkValue::Number(
inv_real.clone() + 1f64,
inv_imaginary.clone(),
inv_unit,
));
// ln(1/z + sqrt(1/z - 1) * sqrt(1/z + 1))
ln(sqrt1.mul_without_unit(sqrt2).add_without_unit(inv_x))
ln(sqrt1.mul_without_unit(sqrt2).add_without_unit(inv))
} else {
KalkNum::new((1f64 / x.value).acosh(), &x.unit)
KalkValue::Number((1f64 / real).acosh(), float!(0), unit)
}
}
pub fn asin(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value > 1f64 || x.value < -1f64 {
pub fn asin(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real > 1f64 || real < -1f64 {
// i * ln(sqrt(1 - z²) - iz)
let root =
sqrt(KalkNum::from(1f64).sub_without_unit(x.clone().mul_without_unit(x.clone())));
sqrt(KalkValue::from(1f64).sub_without_unit(x.clone().mul_without_unit(x.clone())));
let iz = multiply_with_i(x.clone());
let ln = ln(root.sub_without_unit(iz));
multiply_with_i(ln)
} else {
KalkNum::new(x.value.asin(), &x.unit)
KalkValue::Number(real.asin(), float!(0), unit)
}
}
pub fn asinh(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
let x2 = x.clone().mul_without_unit(x.clone());
let sqrt = sqrt(KalkNum::new_with_imaginary(
x2.value + 1f64,
&x2.unit,
x2.imaginary_value,
));
pub fn asinh(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 {
let (x2_real, x2_imaginary, x2_unit) =
as_number_or_return!(x.clone().mul_without_unit(x.clone()));
let sqrt = sqrt(KalkValue::Number(x2_real + 1f64, x2_imaginary, x2_unit));
ln(x.add_without_unit(sqrt))
} else {
KalkNum::new(x.value.asinh(), &x.unit)
KalkValue::Number(real.asinh(), float!(0), unit)
}
}
pub fn atan(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
let iz = multiply_with_i(x);
pub fn atan(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 {
let (iz_real, iz_imaginary, iz_unit) = as_number_or_return!(multiply_with_i(x));
// 1 - iz
let neg = KalkNum::new_with_imaginary(
1f64 - iz.value.clone(),
&iz.unit,
-iz.imaginary_value.clone(),
let neg = KalkValue::Number(
1f64 - iz_real.clone(),
-iz_imaginary.clone(),
iz_unit.clone(),
);
// 1 + iz
let pos = KalkNum::new_with_imaginary(1f64 + iz.value, &iz.unit, iz.imaginary_value);
let pos = KalkValue::Number(1f64 + iz_real, iz_imaginary, iz_unit);
// ln(1 - iz) - ln(1 + iz)
let ln = ln(neg).sub_without_unit(ln(pos));
multiply_with_i(ln).div_without_unit(KalkNum::from(2f64))
multiply_with_i(ln).div_without_unit(KalkValue::from(2f64))
} else {
KalkNum::new(x.value.atan(), &x.unit)
KalkValue::Number(real.atan(), float!(0), unit)
}
}
pub fn atanh(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value >= 1f64 || x.value <= -1f64 {
pub fn atanh(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
if imaginary != 0f64 || real >= 1f64 || real <= -1f64 {
// 1/2 * log(z + 1) - 1/2 * log(-z + 1)
let log1 = ln(KalkNum::new_with_imaginary(
1f64 + x.value.clone(),
&x.unit,
x.imaginary_value.clone(),
));
let log2 = ln(KalkNum::new_with_imaginary(
1f64 - x.value,
&x.unit,
-x.imaginary_value,
let log1 = ln(KalkValue::Number(
1f64 + real.clone(),
imaginary.clone(),
unit.clone(),
));
let log2 = ln(KalkValue::Number(1f64 - real, -imaginary, unit));
log1.sub_without_unit(log2)
.div_without_unit(KalkNum::from(2f64))
.div_without_unit(KalkValue::from(2f64))
} else {
KalkNum::new(x.value.atanh(), &x.unit)
KalkValue::Number(real.atanh(), float!(0), unit)
}
}
pub fn cbrt(x: KalkNum) -> KalkNum {
KalkNum::new(x.value.cbrt(), &x.unit)
pub fn cbrt(x: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
KalkValue::Number(real.cbrt(), float!(0), unit)
}
pub fn ceil(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(x.value.ceil(), &x.unit, x.imaginary_value.ceil())
pub fn ceil(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(real.ceil(), imaginary.ceil(), unit)
}
pub fn cos(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(
x.value.clone().cos() * x.imaginary_value.clone().cosh(),
&x.unit,
-x.value.sin() * x.imaginary_value.sinh(),
pub fn cos(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(
real.clone().cos() * imaginary.clone().cosh(),
-real.sin() * imaginary.sinh(),
unit,
)
}
pub fn cosh(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(
x.value.clone().cosh() * x.imaginary_value.clone().cos(),
&x.unit,
x.value.sinh() * x.imaginary_value.sin(),
pub fn cosh(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(
real.clone().cosh() * imaginary.clone().cos(),
real.sinh() * imaginary.sin(),
unit,
)
}
pub fn csc(x: KalkNum) -> KalkNum {
KalkNum::from(1f64).div_without_unit(sin(x))
pub fn csc(x: KalkValue) -> KalkValue {
KalkValue::from(1f64).div_without_unit(sin(x))
}
pub fn csch(x: KalkNum) -> KalkNum {
KalkNum::from(1f64).div_without_unit(sinh(x))
pub fn csch(x: KalkValue) -> KalkValue {
KalkValue::from(1f64).div_without_unit(sinh(x))
}
pub fn cot(x: KalkNum) -> KalkNum {
let a = x.value * 2f64;
let b = x.imaginary_value * 2f64;
KalkNum::new_with_imaginary(
pub fn cot(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
let a = real * 2f64;
let b = imaginary * 2f64;
KalkValue::Number(
-a.clone().sin() / (a.clone().cos() - b.clone().cosh()),
&x.unit,
b.clone().sinh() / (a.cos() - b.cosh()),
unit,
)
}
pub fn coth(x: KalkNum) -> KalkNum {
let a = x.value * 2f64;
let b = x.imaginary_value * 2f64;
KalkNum::new_with_imaginary(
pub fn coth(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
let a = real * 2f64;
let b = imaginary * 2f64;
KalkValue::Number(
-a.clone().sinh() / (b.clone().cos() - a.clone().cosh()),
&x.unit,
b.clone().sin() / (b.cos() - a.cosh()),
unit,
)
}
pub fn exp(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
pub fn exp(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
if imaginary != 0f64 {
// e^a*cos(b) + ie^a*sin(b)
let exp_a = x.value.exp();
let b = x.imaginary_value;
KalkNum::new_with_imaginary(exp_a.clone() * b.clone().cos(), &x.unit, exp_a * b.sin())
let exp_a = real.exp();
let b = imaginary;
KalkValue::Number(exp_a.clone() * b.clone().cos(), exp_a * b.sin(), unit)
} else {
KalkNum::new(x.value.exp(), &x.unit)
KalkValue::Number(real.exp(), float!(0), unit)
}
}
pub fn floor(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(x.value.floor(), &x.unit, x.imaginary_value.floor())
pub fn floor(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(real.floor(), imaginary.floor(), unit)
}
pub fn frac(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(x.value.fract(), &x.unit, x.imaginary_value.fract())
pub fn frac(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(real.fract(), imaginary.fract(), unit)
}
pub fn gcd(x: KalkNum, y: KalkNum) -> KalkNum {
pub fn gcd(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
let (real_rhs, imaginary_rhs, _) = as_number_or_return!(y.clone());
// Find the norm of a Gaussian integer
fn norm(x: KalkNum) -> KalkNum {
KalkNum::new(
(x.value.clone() * x.value) + (x.imaginary_value.clone() * x.imaginary_value),
&x.unit,
fn norm(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(
(real.clone() * real) + (imaginary.clone() * imaginary),
float!(0),
unit,
)
}
if x.has_imaginary() || y.has_imaginary() {
if x.value.clone().fract() != 0f64
|| y.value.clone().fract() != 0f64
|| x.imaginary_value.clone().fract() != 0f64
|| y.imaginary_value.clone().fract() != 0f64
if imaginary != 0f64 || y.has_imaginary() {
if real.clone().fract() != 0f64
|| real_rhs.clone().fract() != 0f64
|| imaginary.clone().fract() != 0f64
|| imaginary_rhs.clone().fract() != 0f64
{
// Not a Gaussian integer!
// TODO: throw an actual error instead of returning NaN
return KalkNum::from(f64::NAN);
return KalkValue::from(f64::NAN);
}
// Partially derived from:
@ -537,7 +573,7 @@ pub mod funcs {
let b;
// Ensure a > b
if norm(x.clone()).value < norm(y.clone()).value {
if norm(x.clone()).values().0 < norm(y.clone()).values().0 {
a = y;
b = x;
} else {
@ -545,43 +581,46 @@ pub mod funcs {
b = y;
}
let mut c = a.clone().div_without_unit(b.clone());
if c.imaginary_value.clone().fract() == 0f64 {
KalkNum::new_with_imaginary(b.value.abs(), &b.unit, b.imaginary_value)
let (b_real, b_imaginary, b_unit) = as_number_or_return!(b.clone());
let (c_real, c_imaginary, c_unit) =
as_number_or_return!(a.clone().div_without_unit(b.clone()));
if c_imaginary.clone().fract() == 0f64 {
KalkValue::Number(b_real.abs(), b_imaginary, b_unit)
} else {
c.value = c.value.round();
c.imaginary_value = c.imaginary_value.round();
gcd(a.sub_without_unit(b.clone().mul_without_unit(c)), b)
let rounded_c = KalkValue::Number(c_real.round(), c_imaginary.round(), c_unit);
gcd(a.sub_without_unit(b.clone().mul_without_unit(rounded_c)), b)
}
} else {
if x.value < 0f64 || y.value < 0f64 {
if real < 0f64 || real_rhs < 0f64 {
return gcd(
KalkNum::new(x.value.abs(), &x.unit),
KalkNum::new(y.value.abs(), &y.unit),
KalkValue::Number(real.abs(), float!(0), unit.clone()),
KalkValue::Number(real_rhs.abs(), float!(0), unit),
);
}
// Euclidean GCD algorithm, but with modulus
let mut x_a = x.clone();
let mut y_a = y.clone();
while !y_a.value.eq(&0f64) {
let t = y_a.value.clone();
y_a.value = x_a.value % y_a.value;
x_a.value = t;
let mut x_a = real.clone();
let mut y_a = real_rhs.clone();
while !y_a.eq(&0f64) {
let t = y_a.clone();
y_a = x_a % y_a;
x_a = t;
}
// Usually we'd need to return max(x, -x), but since we've handled negative
// values above, that is unnecessary.
x_a
KalkValue::Number(x_a, float!(0), unit)
}
}
pub fn im(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(x.value, "", KalkNum::default().value)
pub fn im(x: KalkValue) -> KalkValue {
let (_, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(imaginary, float!(0), unit)
}
pub fn inverson(x: KalkNum) -> KalkNum {
KalkNum::from(if let Some(boolean_value) = x.boolean_value {
pub fn iverson(x: KalkValue) -> KalkValue {
KalkValue::from(if let KalkValue::Boolean(boolean_value) = x {
if boolean_value {
1
} else {
@ -597,165 +636,186 @@ pub mod funcs {
// lcm(a, b) = ⎜ ───────── ⎟ × ⎜b⎜
// ⎜ gcd(a, b) ⎟
// ⎝ ⎠
pub fn lcm(x: KalkNum, y: KalkNum) -> KalkNum {
pub fn lcm(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
let (real_rhs, imaginary_rhs, unit_rhs) = as_number_or_return!(y.clone());
let gcd = gcd(x.clone(), y.clone());
let absx = KalkNum::new_with_imaginary(x.value.abs(), &x.unit, x.imaginary_value);
let absy = KalkNum::new_with_imaginary(y.value.abs(), &y.unit, y.imaginary_value);
let absx = KalkValue::Number(real.abs(), imaginary, unit);
let absy = KalkValue::Number(real_rhs.abs(), imaginary_rhs, unit_rhs);
return absx.div_without_unit(gcd).mul_without_unit(absy);
}
pub fn log(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value < 0f64 {
pub fn log(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real < 0f64 {
// ln(z) / ln(10)
ln(x).div_without_unit(KalkNum::from(10f64.ln()))
ln(x).div_without_unit(KalkValue::from(10f64.ln()))
} else {
KalkNum::new(x.value.log10(), &x.unit)
KalkValue::Number(real.log10(), float!(0), unit)
}
}
pub fn logx(x: KalkNum, y: KalkNum) -> KalkNum {
if x.has_imaginary() || y.has_imaginary() || x.value < 0f64 || y.value < 0f64 {
pub fn logx(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
let (real_rhs, _, _) = as_number_or_return!(y.clone());
if imaginary != 0f64 || y.has_imaginary() || real < 0f64 || real_rhs < 0f64 {
// ln(z) / ln(n)
ln(x).div_without_unit(ln(y))
} else {
KalkNum::new(x.value.log10() / y.value.log10(), &x.unit)
KalkValue::Number(real.log10() / real_rhs.log10(), float!(0), unit)
}
}
pub fn ln(x: KalkNum) -> KalkNum {
if x.has_imaginary() || x.value < 0f64 {
pub fn ln(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 || real < 0f64 {
let r = abs(x.clone());
// ln|z| + i * arg z
ln(r).add_without_unit(multiply_with_i(arg(x)))
} else {
KalkNum::new(x.value.ln(), &x.unit)
KalkValue::Number(real.ln(), float!(0), unit)
}
}
pub fn nth_root(x: KalkNum, n: KalkNum) -> KalkNum {
x.pow_without_unit(KalkNum::from(1f64).div_without_unit(n))
pub fn nth_root(x: KalkValue, n: KalkValue) -> KalkValue {
x.pow_without_unit(KalkValue::from(1f64).div_without_unit(n))
}
pub fn re(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(KalkNum::default().value, "", x.imaginary_value)
pub fn re(x: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
KalkValue::Number(real, float!(0), unit)
}
pub fn round(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(x.value.round(), &x.unit, x.imaginary_value.round())
pub fn round(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(real.round(), imaginary.round(), unit)
}
pub fn sec(x: KalkNum) -> KalkNum {
KalkNum::from(1f64).div_without_unit(cos(x))
pub fn sec(x: KalkValue) -> KalkValue {
KalkValue::from(1f64).div_without_unit(cos(x))
}
pub fn sech(x: KalkNum) -> KalkNum {
KalkNum::from(1f64).div_without_unit(cosh(x))
pub fn sech(x: KalkValue) -> KalkValue {
KalkValue::from(1f64).div_without_unit(cosh(x))
}
pub fn sin(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(
x.value.clone().sin() * x.imaginary_value.clone().cosh(),
&x.unit,
x.value.cos() * x.imaginary_value.sinh(),
pub fn sin(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(
real.clone().sin() * imaginary.clone().cosh(),
real.cos() * imaginary.sinh(),
unit,
)
}
pub fn sinh(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(
x.value.clone().sinh() * x.imaginary_value.clone().cos(),
&x.unit,
x.value.cosh() * x.imaginary_value.sin(),
pub fn sinh(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(
real.clone().sinh() * imaginary.clone().cos(),
real.cosh() * imaginary.sin(),
unit,
)
}
pub fn sqrt(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
let abs = abs(x.clone());
let r = abs.value;
let a = x.value;
let b = x.imaginary_value;
pub fn sqrt(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
if imaginary != 0f64 {
let (abs_real, _, abs_unit) = as_number_or_return!(abs(x.clone()));
let r = abs_real;
let a = real;
let b = imaginary;
// sqrt((|z| + a) / 2) + i * (b / |b|) * sqrt((|z| - a) / 2)
KalkNum::new_with_imaginary(
KalkValue::Number(
((r.clone() + a.clone()) / 2f64).sqrt(),
&abs.unit,
(b.clone() / b.abs()) * ((r - a) / 2f64).sqrt(),
abs_unit,
)
} else if x.value < 0f64 {
KalkNum::from_imaginary(x.value.abs().sqrt())
} else if real < 0f64 {
KalkValue::Number(float!(0), real.abs().sqrt(), unit)
} else {
KalkNum::new(x.value.sqrt(), &x.unit)
KalkValue::Number(real.sqrt(), float!(0), unit)
}
}
pub fn tan(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
let a = x.value * 2f64;
let b = x.imaginary_value * 2f64;
KalkNum::new_with_imaginary(
pub fn tan(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
if imaginary != 0f64 {
let a = real * 2f64;
let b = imaginary * 2f64;
KalkValue::Number(
a.clone().sin() / (a.clone().cos() + b.clone().cosh()),
&x.unit,
b.clone().sinh() / (a.cos() + b.cosh()),
unit,
)
} else {
KalkNum::new(x.value.tan(), &x.unit)
KalkValue::Number(real.tan(), float!(0), unit)
}
}
pub fn tanh(x: KalkNum) -> KalkNum {
if x.has_imaginary() {
let a = x.value * 2f64;
let b = x.imaginary_value * 2f64;
KalkNum::new_with_imaginary(
pub fn tanh(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
if imaginary != 0f64 {
let a = real * 2f64;
let b = imaginary * 2f64;
KalkValue::Number(
a.clone().sinh() / (a.clone().cosh() + b.clone().cos()),
&x.unit,
b.clone().sin() / (a.cosh() + b.cos()),
unit,
)
} else {
KalkNum::new(x.value.tanh(), &x.unit)
KalkValue::Number(real.tanh(), float!(0), unit)
}
}
pub fn trunc(x: KalkNum) -> KalkNum {
KalkNum::new_with_imaginary(x.value.trunc(), &x.unit, x.imaginary_value.trunc())
pub fn trunc(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(real.trunc(), imaginary.trunc(), unit)
}
pub fn ncr(x: KalkNum, y: KalkNum) -> KalkNum {
pub fn ncr(x: KalkValue, y: KalkValue) -> KalkValue {
factorial(x.clone()).div_without_unit(
factorial(y.clone()).mul_without_unit(factorial(x.sub_without_unit(y))),
)
}
pub fn npr(x: KalkNum, y: KalkNum) -> KalkNum {
pub fn npr(x: KalkValue, y: KalkValue) -> KalkValue {
factorial(x.clone()).div_without_unit(factorial(x.sub_without_unit(y)))
}
fn multiply_with_i(z: KalkNum) -> KalkNum {
fn multiply_with_i(z: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(z);
// iz = i(a + bi) = -b + ai
KalkNum::new_with_imaginary(-z.imaginary_value, &z.unit, z.value)
KalkValue::Number(-imaginary, real, unit)
}
}
#[cfg(test)]
mod tests {
use super::funcs::*;
use crate::prelude::KalkNum;
use crate::float;
use crate::prelude::KalkValue;
use crate::test_helpers::cmp;
#[test]
fn test_unary_funcs() {
let in_out = vec![
(abs as fn(KalkNum) -> KalkNum, (3f64, 4f64), (5f64, 0f64)),
(
abs as fn(KalkValue) -> KalkValue,
(3f64, 4f64),
(5f64, 0f64),
),
(abs, (-3f64, 4f64), (5f64, 0f64)),
(abs, (3f64, -4f64), (5f64, 0f64)),
(abs, (-3f64, 0f64), (3f64, 0f64)),
];
for (i, (func, input, expected_output)) in in_out.iter().enumerate() {
let actual_output = func(KalkNum::new_with_imaginary(
KalkNum::from(input.0).value,
"",
KalkNum::from(input.1).value,
let actual_output = func(KalkValue::Number(
float!(input.0),
float!(input.1),
String::new(),
));
println!(
@ -777,7 +837,7 @@ mod tests {
fn test_binary_funcs() {
let in_out = vec![
(
gcd as fn(KalkNum, KalkNum) -> KalkNum,
gcd as fn(KalkValue, KalkValue) -> KalkValue,
((12f64, 0f64), (18f64, 0f64)),
(6f64, 0f64),
),
@ -792,16 +852,8 @@ mod tests {
for (i, (func, input, expected_output)) in in_out.iter().enumerate() {
let actual_output = func(
KalkNum::new_with_imaginary(
KalkNum::from(input.0 .0).value,
"",
KalkNum::from(input.0 .1).value,
),
KalkNum::new_with_imaginary(
KalkNum::from(input.1 .0).value,
"",
KalkNum::from(input.1 .1).value,
),
KalkValue::Number(float!(input.0 .0), float!(input.0 .1), String::new()),
KalkValue::Number(float!(input.1 .0), float!(input.1 .1), String::new()),
);
println!(
@ -824,7 +876,7 @@ mod tests {
// Auto-generated using kalk/scripts/generate_funcs_test_cases.py
let in_out = vec![
(
arg as fn(KalkNum) -> KalkNum,
arg as fn(KalkValue) -> KalkValue,
(0.3f64, 0f64),
(0.0f64, 0.0f64),
),
@ -1047,10 +1099,10 @@ mod tests {
];
for (i, (func, input, expected_output)) in in_out.iter().enumerate() {
let actual_output = func(KalkNum::new_with_imaginary(
KalkNum::from(input.0).value,
"",
KalkNum::from(input.1).value,
let actual_output = func(KalkValue::Number(
float!(input.0),
float!(input.1),
String::new(),
));
let expected_has_nan_or_inf = expected_output.0.is_nan()

View File

@ -1,29 +1,38 @@
pub mod special_funcs {
use crate::kalk_num::KalkNum;
use crate::{as_number_or_return, float, kalk_value::KalkValue};
pub fn factorial(x: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
pub fn factorial(x: KalkNum) -> KalkNum {
// Round it a bit, to prevent floating point errors.
KalkNum::new(
(super::funcs::precise_gamma(x.value + 1f64) * 10e6f64).round() / 10e6f64,
&x.unit,
KalkValue::Number(
(super::funcs::precise_gamma(real + 1f64) * 10e6f64).round() / 10e6f64,
float!(0),
unit,
)
}
}
pub(crate) mod funcs {
use crate::kalk_num::KalkNum;
use crate::kalk_value::KalkValue;
use crate::prelude::funcs::abs;
use crate::{as_number_or_return, float};
pub fn arg(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
pub fn arg(x: KalkNum) -> KalkNum {
// i(ln|x| - ln(x))
KalkNum::new(x.imaginary_value.atan2(x.value), &x.unit)
KalkValue::Number(imaginary.atan2(real), float!(0), unit)
}
pub fn gamma(x: KalkNum) -> KalkNum {
pub fn gamma(x: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
// Round it a bit, to prevent floating point errors.
KalkNum::new(
(precise_gamma(x.value) * 10e6f64).round() / 10e6f64,
&x.unit,
KalkValue::Number(
(precise_gamma(real) * 10e6f64).round() / 10e6f64,
float!(0),
unit,
)
}
@ -50,34 +59,49 @@ pub(crate) mod funcs {
2f64.sqrt() * pi.sqrt() * t.powf(x - 0.5f64) * (-t).exp() * a
}
pub fn bitcmp(x: KalkNum) -> KalkNum {
KalkNum::from(!(x.value.round() as i32))
pub fn bitcmp(x: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
KalkValue::from(!(real.round() as i32))
}
pub fn bitand(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::from(x.value.round() as i32 & y.value.round() as i32)
pub fn bitand(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::from(real.round() as i32 & real_rhs.round() as i32)
}
pub fn bitor(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::from(x.value.round() as i32 | y.value.round() as i32)
pub fn bitor(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::from(real.round() as i32 | real_rhs.round() as i32)
}
pub fn bitxor(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::from(x.value.round() as i32 ^ y.value.round() as i32)
pub fn bitxor(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::from(real.round() as i32 ^ real_rhs.round() as i32)
}
pub fn bitshift(x: KalkNum, y: KalkNum) -> KalkNum {
let x = x.value.round() as i32;
let y = y.value.round() as i32;
pub fn bitshift(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
let x = real.round() as i32;
let y = real_rhs.round() as i32;
if y < 0 {
KalkNum::from((x >> y.abs()))
KalkValue::from(x >> y.abs())
} else {
KalkNum::from((x << y))
KalkValue::from(x << y)
}
}
pub fn hypot(x: KalkNum, y: KalkNum) -> KalkNum {
if x.has_imaginary() || y.has_imaginary() {
pub fn hypot(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x.clone());
let (real_rhs, imaginary_rhs, _) = as_number_or_return!(y.clone());
if imaginary.abs() != 0f64 || imaginary_rhs != 0f64 {
let abs_x = abs(x);
let abs_y = abs(y);
crate::prelude::funcs::sqrt(
@ -87,15 +111,21 @@ pub(crate) mod funcs {
.add_without_unit(abs_y.clone().mul_without_unit(abs_y)),
)
} else {
KalkNum::new(x.value.hypot(y.value), &x.unit)
KalkValue::Number(real.hypot(real_rhs), float!(0), unit)
}
}
pub fn max(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::new(x.value.max(y.value), &x.unit)
pub fn max(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::Number(real.max(real_rhs), float!(0), unit)
}
pub fn min(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::new(x.value.min(y.value), &x.unit)
pub fn min(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::Number(real.min(real_rhs), float!(0), unit)
}
}

View File

@ -1,59 +1,82 @@
pub mod special_funcs {
use crate::prelude::KalkNum;
use crate::{as_number_or_return, float, prelude::KalkValue};
pub fn factorial(x: KalkNum) -> KalkNum {
KalkNum::new((x.value + 1f64).gamma(), &x.unit)
pub fn factorial(x: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
KalkValue::Number((real + 1f64).gamma(), float!(0), unit)
}
}
pub(crate) mod funcs {
use crate::kalk_num::KalkNum;
use crate::kalk_value::KalkValue;
use crate::prelude::funcs::abs;
use crate::{as_number_or_return, float};
pub fn arg(x: KalkNum) -> KalkNum {
KalkNum::new(x.imaginary_value.atan2(&x.value), &x.unit)
pub fn arg(x: KalkValue) -> KalkValue {
let (real, imaginary, unit) = as_number_or_return!(x);
KalkValue::Number(imaginary.atan2(&real), float!(0), unit)
}
pub fn gamma(x: KalkNum) -> KalkNum {
KalkNum::new(x.value.gamma(), &x.unit)
pub fn gamma(x: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
KalkValue::Number(real.gamma(), float!(0), unit)
}
pub fn bitcmp(x: KalkNum) -> KalkNum {
KalkNum::from(!x.value.to_i32_saturating().unwrap_or(i32::MAX))
pub fn bitcmp(x: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
KalkValue::from(!real.to_i32_saturating().unwrap_or(i32::MAX))
}
pub fn bitand(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::from(
x.value.to_i32_saturating().unwrap_or(i32::MAX)
& y.value.to_i32_saturating().unwrap_or(i32::MAX),
pub fn bitand(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::from(
real.to_i32_saturating().unwrap_or(i32::MAX)
& real_rhs.to_i32_saturating().unwrap_or(i32::MAX),
)
}
pub fn bitor(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::from(
x.value.to_i32_saturating().unwrap_or(i32::MAX)
| y.value.to_i32_saturating().unwrap_or(i32::MAX),
pub fn bitor(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::from(
real.to_i32_saturating().unwrap_or(i32::MAX)
| real_rhs.to_i32_saturating().unwrap_or(i32::MAX),
)
}
pub fn bitxor(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::from(
x.value.to_i32_saturating().unwrap_or(i32::MAX)
^ y.value.to_i32_saturating().unwrap_or(i32::MAX),
pub fn bitxor(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::from(
real.to_i32_saturating().unwrap_or(i32::MAX)
^ real_rhs.to_i32_saturating().unwrap_or(i32::MAX),
)
}
pub fn bitshift(x: KalkNum, y: KalkNum) -> KalkNum {
let x = x.value.to_i32_saturating().unwrap_or(i32::MAX) as i32;
let y = y.value.to_i32_saturating().unwrap_or(i32::MAX) as i32;
pub fn bitshift(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, _) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
let x = real.to_i32_saturating().unwrap_or(i32::MAX) as i32;
let y = real_rhs.to_i32_saturating().unwrap_or(i32::MAX) as i32;
if y < 0 {
KalkNum::from(x >> y.abs())
KalkValue::from(x >> y.abs())
} else {
KalkNum::from(x << y)
KalkValue::from(x << y)
}
}
pub fn hypot(x: KalkNum, y: KalkNum) -> KalkNum {
pub fn hypot(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x.clone());
let (real_rhs, _, _) = as_number_or_return!(y.clone());
if x.has_imaginary() || y.has_imaginary() {
let abs_x = abs(x);
let abs_y = abs(y);
@ -64,15 +87,21 @@ pub(crate) mod funcs {
.add_without_unit(abs_y.clone().mul_without_unit(abs_y)),
)
} else {
KalkNum::new(x.value.hypot(&y.value), &x.unit)
KalkValue::Number(real.hypot(&real_rhs), float!(0), unit)
}
}
pub fn max(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::new(x.value.max(&y.value), &x.unit)
pub fn max(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::Number(real.max(&real_rhs), float!(0), unit)
}
pub fn min(x: KalkNum, y: KalkNum) -> KalkNum {
KalkNum::new(x.value.min(&y.value), &x.unit)
pub fn min(x: KalkValue, y: KalkValue) -> KalkValue {
let (real, _, unit) = as_number_or_return!(x);
let (real_rhs, _, _) = as_number_or_return!(y);
KalkValue::Number(real.min(&real_rhs), float!(0), unit)
}
}

View File

@ -60,7 +60,7 @@ pub fn float_to_radix(value: f64, radix: u8) -> String {
pub fn to_radix_pretty(value: f64, radix: u8) -> String {
if radix == 10 {
crate::kalk_num::format_number(value)
crate::kalk_value::format_number(value)
} else {
format!(
"{}{}",