diff --git a/kalk/src/ast.rs b/kalk/src/ast.rs index 446bb1a..593de8a 100644 --- a/kalk/src/ast.rs +++ b/kalk/src/ast.rs @@ -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()) } } diff --git a/kalk/src/calculation_result.rs b/kalk/src/calculation_result.rs new file mode 100644 index 0000000..072f2de --- /dev/null +++ b/kalk/src/calculation_result.rs @@ -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 { + self.value.estimate() + } +} diff --git a/kalk/src/calculus.rs b/kalk/src/calculus.rs index 57a3742..f52454d 100644 --- a/kalk/src/calculus.rs +++ b/kalk/src/calculus.rs @@ -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 { + argument: KalkValue, +) -> Result { 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 { +) -> Result { 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 { +) -> Result { 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 { - let mut result = KalkNum::default(); +) -> Result { + 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", diff --git a/kalk/src/integration_testing.rs b/kalk/src/integration_testing.rs index c6d630e..712d481 100644 --- a/kalk/src/integration_testing.rs +++ b/kalk/src/integration_testing.rs @@ -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] diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index e7ab16e..30540bb 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -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) -> Result, CalcError> { + pub fn interpret( + &mut self, + statements: Vec, + ) -> Result, 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 { +fn eval_stmt(context: &mut Context, stmt: &Stmt) -> Result { 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 { } } -fn eval_var_decl_stmt(context: &mut Context, stmt: &Stmt) -> Result { +fn eval_var_decl_stmt(context: &mut Context, stmt: &Stmt) -> Result { context.symbol_table.insert(stmt.clone()); - Ok(KalkNum::from(1)) + Ok(KalkValue::from(1)) } -fn eval_fn_decl_stmt() -> Result { - 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 { + 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 { - Ok(KalkNum::from(1)) +fn eval_unit_decl_stmt() -> Result { + Ok(KalkValue::from(1)) } -fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result { +fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result { eval_expr(context, &expr, "") } @@ -101,7 +105,7 @@ pub(crate) fn eval_expr( context: &mut Context, expr: &Expr, unit: &str, -) -> Result { +) -> Result { #[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 { +) -> Result { 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 { +) -> Result { 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 { +) -> Result { 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 { +) -> Result { 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 { +) -> Result { // 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 { - 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 { + 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 { +#[allow(unused_variables)] +#[cfg(not(feature = "rug"))] +fn eval_literal_expr( + context: &mut Context, + value: f64, + unit: &str, +) -> Result { + Ok(KalkValue::Number( + float!(value), + float!(0), + unit.to_string(), + )) +} + +fn eval_group_expr(context: &mut Context, expr: &Expr, unit: &str) -> Result { eval_expr(context, expr, unit) } @@ -273,7 +291,7 @@ pub(crate) fn eval_fn_call_expr( identifier: &Identifier, expressions: &[Expr], unit: &str, -) -> Result { +) -> Result { // 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, unit: &str, -) -> Result { +) -> Result { 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, CalcError> { + fn interpret_with_unit(stmt: Stmt) -> Result, 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, CalcError> { + fn interpret(stmt: Stmt) -> Result, 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 )); } diff --git a/kalk/src/kalk_num/regular.rs b/kalk/src/kalk_num/regular.rs deleted file mode 100644 index 526539b..0000000 --- a/kalk/src/kalk_num/regular.rs +++ /dev/null @@ -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, - pub(crate) other_radix: Option, -} - -#[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 { - 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 for KalkNum { - fn from(x: f64) -> Self { - KalkNum::new(x, "") - } -} - -impl From for KalkNum { - fn from(x: f32) -> Self { - KalkNum::new(x as f64, "") - } -} - -impl From for KalkNum { - fn from(x: i128) -> Self { - KalkNum::new(x as f64, "") - } -} - -impl From for KalkNum { - fn from(x: i64) -> Self { - KalkNum::new(x as f64, "") - } -} - -impl From for KalkNum { - fn from(x: i32) -> Self { - KalkNum::new(x as f64, "") - } -} diff --git a/kalk/src/kalk_num/with_rug.rs b/kalk/src/kalk_num/with_rug.rs deleted file mode 100644 index 72f0a89..0000000 --- a/kalk/src/kalk_num/with_rug.rs +++ /dev/null @@ -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, - pub(crate) other_radix: Option, -} - -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 for KalkNum { - fn from(x: f64) -> Self { - KalkNum::new(Float::with_val(63, x), "") - } -} - -impl From for KalkNum { - fn from(x: f32) -> Self { - KalkNum::new(Float::with_val(63, x), "") - } -} - -impl From for KalkNum { - fn from(x: i128) -> Self { - KalkNum::new(Float::with_val(63, x), "") - } -} - -impl From for KalkNum { - fn from(x: i64) -> Self { - KalkNum::new(Float::with_val(63, x), "") - } -} - -impl From for KalkNum { - fn from(x: i32) -> Self { - KalkNum::new(Float::with_val(63, x), "") - } -} diff --git a/kalk/src/kalk_num/mod.rs b/kalk/src/kalk_value/mod.rs similarity index 54% rename from kalk/src/kalk_num/mod.rs rename to kalk/src/kalk_value/mod.rs index 7a1de32..4562d1d 100644 --- a/kalk/src/kalk_num/mod.rs +++ b/kalk/src/kalk_value/mod.rs @@ -1,8 +1,6 @@ #[cfg(feature = "rug")] pub mod with_rug; #[cfg(feature = "rug")] -use rug::ops::Pow; -#[cfg(feature = "rug")] use rug::Float; #[cfg(feature = "rug")] pub use with_rug::*; @@ -12,6 +10,8 @@ pub mod regular; #[cfg(not(feature = "rug"))] pub use regular::*; +mod rounding; + use crate::ast::Expr; use crate::radix; use lazy_static::lazy_static; @@ -49,6 +49,70 @@ lazy_static! { }; } +#[macro_export] +#[cfg(not(feature = "rug"))] +macro_rules! float { + ($x:expr) => {{ + $x.clone() as f64 + }}; +} + +#[macro_export] +#[cfg(feature = "rug")] +macro_rules! float { + ($x:expr) => {{ + use rug::Float; + Float::with_val(63, $x) + }}; +} + +#[macro_export] +#[cfg(not(feature = "rug"))] +macro_rules! primitive { + ($x:expr) => {{ + $x.clone() + }}; +} + +#[macro_export] +#[cfg(feature = "rug")] +macro_rules! primitive { + ($x:expr) => {{ + $x.to_f64() + }}; +} + +#[macro_export] +macro_rules! as_number_or_return { + ($x:expr) => {{ + if let KalkValue::Number(real, imaginary, unit) = $x { + ( + real, + if imaginary == -0 { + float!(0) + } else { + imaginary + }, + unit, + ) + } else { + return KalkValue::nan(); + } + }}; +} + +#[macro_export] +macro_rules! as_number_or_zero { + ($x:expr) => {{ + use crate::float; + if let KalkValue::Number(real, imaginary, unit) = $x { + (real, imaginary, unit) + } else { + (float!(0), float!(0), String::new()) + } + }}; +} + #[wasm_bindgen] #[derive(Clone)] pub struct ScientificNotation { @@ -86,66 +150,71 @@ impl ScientificNotation { } } -impl KalkNum { - pub fn has_real(&self) -> bool { - self.value != 0f64 - } +#[derive(PartialEq, Debug, Clone)] +pub enum KalkValue { + #[cfg(not(feature = "rug"))] + Number(f64, f64, String), + #[cfg(feature = "rug")] + Number(Float, Float, String), + Boolean(bool), + Vector(Vec), +} - pub fn has_imaginary(&self) -> bool { - self.imaginary_value != 0f64 - } - - pub fn to_scientific_notation( - &self, - complex_number_type: ComplexNumberType, - ) -> ScientificNotation { - let value = match complex_number_type { - ComplexNumberType::Real => self.to_f64(), - ComplexNumberType::Imaginary => self.imaginary_to_f64(), - }; - let exponent = value.clone().abs().log10().floor() as i32 + 1; - - ScientificNotation { - negative: value < 0f64, - value: value / (10f64.powf(exponent as f64 - 1f64) as f64), - // I... am not sure what else to do... - exponent, - imaginary: complex_number_type == ComplexNumberType::Imaginary, - } +impl KalkValue { + pub fn nan() -> Self { + KalkValue::Number(float!(f64::NAN), float!(0f64), String::new()) } pub fn to_string(&self) -> String { - let as_str = format_number(self.to_f64()); + match self { + KalkValue::Number(real, imaginary, _) => { + let as_str = format_number(primitive!(real)); - if self.has_imaginary() { - let imaginary_as_str = format_number(self.imaginary_to_f64().abs()); - let sign = if self.imaginary_value < 0f64 { - "-" - } else { - "+" - }; + if self.has_imaginary() { + let imaginary_as_str = format_number(primitive!(imaginary).abs()); + let sign = if imaginary < &0f64 { "-" } else { "+" }; - format!("{} {} {}i", as_str, sign, imaginary_as_str) - } else { - as_str + format!("{} {} {}i", as_str, sign, imaginary_as_str) + } else { + as_str + } + } + KalkValue::Boolean(is_true) => { + if *is_true { + String::from("true") + } else { + String::from("false") + } + } + KalkValue::Vector(values) => { + let mut result = String::from("["); + for value in values { + result.push_str(&value.to_string()); + result.push_str(", "); + } + + if values.len() > 0 { + result.pop(); + result.pop(); + } + + result.push_str("]"); + + result + } } } pub fn to_string_big(&self) -> String { - if !self.has_imaginary() { - self.value.to_string() + if let KalkValue::Number(real, imaginary, _) = self { + if !self.has_imaginary() { + return real.to_string(); + } + + let sign = if imaginary < &0f64 { "-" } else { "+" }; + format!("{} {} {}", real.to_string(), sign, imaginary.to_string()) } else { - let sign = if self.imaginary_value < 0f64 { - "-" - } else { - "+" - }; - format!( - "{} {} {}", - self.value.to_string(), - sign, - self.imaginary_value.to_string() - ) + self.to_string() } } @@ -166,10 +235,11 @@ impl KalkNum { } } - fn to_string_pretty_radix(&self, radix: u8) -> String { - if let Some(boolean_value) = self.boolean_value { - return boolean_value.to_string(); - } + pub(crate) fn to_string_pretty_radix(&self, radix: u8) -> String { + let (real, imaginary, unit) = match self { + KalkValue::Number(real, imaginary, unit) => (real, imaginary, unit), + _ => return self.to_string(), + }; let real_f64 = self.to_f64(); let imaginary_f64 = self.imaginary_to_f64(); @@ -182,11 +252,12 @@ impl KalkNum { } let sci_notation_real = self.to_scientific_notation(ComplexNumberType::Real); - let mut adjusted_num = self.clone(); - let result_str = if (-6..8).contains(&sci_notation_real.exponent) || self.value == 0f64 { + let mut new_real = real.clone(); + let mut new_imaginary = imaginary.clone(); + let result_str = if (-6..8).contains(&sci_notation_real.exponent) || real == &0f64 { self.to_string_real(radix) } else if sci_notation_real.exponent <= -14 { - adjusted_num.value = KalkNum::from(0f64).value; + new_real = float!(0); String::from("0") } else { if radix == 10 { @@ -198,12 +269,12 @@ impl KalkNum { let sci_notation_imaginary = self.to_scientific_notation(ComplexNumberType::Imaginary); let result_str_imaginary = if (-6..8).contains(&sci_notation_imaginary.exponent) - || self.imaginary_value == 0f64 - || self.imaginary_value == 1f64 + || imaginary == &0f64 + || imaginary == &1f64 { self.to_string_imaginary(radix, true) } else if sci_notation_imaginary.exponent <= -14 { - adjusted_num.imaginary_value = KalkNum::from(0f64).value; + new_imaginary = float!(0); String::from("0") } else { if radix == 10 { @@ -214,7 +285,7 @@ impl KalkNum { }; let mut output = result_str; - if adjusted_num.has_imaginary() && result_str_imaginary != "0" { + if imaginary != &0f64 && new_imaginary != 0f64 && result_str_imaginary != "0" { // If the real value is 0, and there is an imaginary one, // clear the output so that the real value is not shown. if output == "0" { @@ -225,11 +296,7 @@ impl KalkNum { if output.len() > 0 { output.push_str(&format!( " {} {}", - if self.imaginary_value < 0f64 { - "-" - } else { - "+" - }, + if imaginary < &0f64 { "-" } else { "+" }, result_str_imaginary.trim_start_matches("-"), )); } else { @@ -237,12 +304,13 @@ impl KalkNum { } } - let unit = adjusted_num.get_unit(); if unit != "" { output.push_str(&format!(" {}", unit)); } - if let Some(estimate) = adjusted_num.estimate() { + let new_value = KalkValue::Number(new_real, new_imaginary, unit.clone()); + + if let Some(estimate) = new_value.estimate() { if estimate != output && radix == 10 { output.push_str(&format!(" ≈ {}", estimate)); } @@ -252,217 +320,20 @@ impl KalkNum { } pub fn to_string_pretty(&self) -> String { - if let Some(other_radix) = self.other_radix { - let with_other_radix = self.to_string_pretty_radix(other_radix); - if with_other_radix != "" { - return format!("{}\n{}", self.to_string_pretty_radix(10), with_other_radix); - } - } - self.to_string_pretty_radix(10) } - pub fn is_too_big(&self) -> bool { - self.value.is_infinite() - } - pub fn to_string_with_unit(&self) -> String { - format!("{} {}", self.to_string(), self.unit) - } - - pub fn has_unit(&self) -> bool { - self.unit.len() > 0 - } - - pub(crate) fn convert_to_unit( - &self, - context: &mut crate::interpreter::Context, - to_unit: &str, - ) -> Option { - let result = crate::interpreter::convert_unit( - context, - &Expr::Literal(self.to_f64()), - &self.unit, - to_unit, - ); - - if let Ok(num) = result { - Some(num) - } else { - None + match self { + KalkValue::Number(_, _, unit) => format!("{} {}", self.to_string(), unit), + _ => self.to_string(), } } - pub(crate) fn add(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.add_without_unit(right) - } - - pub(crate) fn sub(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.sub_without_unit(right) - } - - pub(crate) fn mul(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.mul_without_unit(right) - } - - pub(crate) fn div(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); - self.div_without_unit(right) - } - - pub(crate) fn rem(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - KalkNum::new(self.value % right.value, &right.unit) - } - - pub(crate) fn eq(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.eq_without_unit(&right) - } - - pub(crate) fn not_eq(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.not_eq_without_unit(&right) - } - - pub(crate) fn greater_than( - self, - context: &mut crate::interpreter::Context, - rhs: KalkNum, - ) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.greater_than_without_unit(&right) - } - - pub(crate) fn less_than( - self, - context: &mut crate::interpreter::Context, - rhs: KalkNum, - ) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); - self.less_than_without_unit(&right) - } - - pub(crate) fn greater_or_equals( - self, - context: &mut crate::interpreter::Context, - rhs: KalkNum, - ) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); - let greater = self - .greater_than_without_unit(&right) - .boolean_value - .unwrap(); - let equal = self.eq_without_unit(&right).boolean_value.unwrap(); - KalkNum::from_bool(greater || equal) - } - - pub(crate) fn less_or_equals( - self, - context: &mut crate::interpreter::Context, - rhs: KalkNum, - ) -> KalkNum { - let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); - let less = self.less_than_without_unit(&right).boolean_value.unwrap(); - let equal = self.eq_without_unit(&right).boolean_value.unwrap(); - KalkNum::from_bool(less || equal) - } - - pub(crate) fn add_without_unit(self, rhs: KalkNum) -> KalkNum { - KalkNum::new_with_imaginary( - self.value + rhs.value, - &rhs.unit, - self.imaginary_value + rhs.imaginary_value, - ) - } - - pub(crate) fn sub_without_unit(self, rhs: KalkNum) -> KalkNum { - KalkNum::new_with_imaginary( - self.value - rhs.value, - &rhs.unit, - self.imaginary_value - rhs.imaginary_value, - ) - } - - pub(crate) fn mul_without_unit(self, rhs: KalkNum) -> KalkNum { - // (a + bi)(c + di) = ac + adi + bci + bdi² - KalkNum::new_with_imaginary( - self.value.clone() * rhs.value.clone() - - self.imaginary_value.clone() * rhs.imaginary_value.clone(), - &rhs.unit, - self.value * rhs.imaginary_value + self.imaginary_value * rhs.value, - ) - } - - pub(crate) fn div_without_unit(self, rhs: KalkNum) -> KalkNum { - // Avoid unecessary calculations - if self.imaginary_value == 0f64 && rhs.imaginary_value == 0f64 { - KalkNum::new(self.value / rhs.value, &rhs.unit) - } else { - // Multiply both the numerator and denominator - // with the conjugate of the denominator, and divide. - let conjugate = rhs.get_conjugate(); - let numerator = self.clone().mul_without_unit(conjugate.clone()); - let denominator = rhs.clone().mul_without_unit(conjugate); - KalkNum::new_with_imaginary( - numerator.value / denominator.value.clone(), - &rhs.unit, - numerator.imaginary_value / denominator.value, - ) - } - } - - pub(crate) fn eq_without_unit(&self, rhs: &KalkNum) -> KalkNum { - if self.has_imaginary() || rhs.has_imaginary() { - return KalkNum::from(f64::NAN); - } - - KalkNum::from_bool( - (self.value.clone() - rhs.value.clone()).abs() < ACCEPTABLE_COMPARISON_MARGIN, - ) - } - - pub(crate) fn not_eq_without_unit(&self, rhs: &KalkNum) -> KalkNum { - if self.has_imaginary() || rhs.has_imaginary() { - return KalkNum::from(f64::NAN); - } - - KalkNum::from_bool( - (self.value.clone() - rhs.value.clone()).abs() > ACCEPTABLE_COMPARISON_MARGIN, - ) - } - - pub(crate) fn greater_than_without_unit(&self, rhs: &KalkNum) -> KalkNum { - if self.has_imaginary() || rhs.has_imaginary() { - return KalkNum::from(f64::NAN); - } - - KalkNum::from_bool(self.value.clone() - rhs.value.clone() > ACCEPTABLE_COMPARISON_MARGIN) - } - - pub(crate) fn less_than_without_unit(&self, rhs: &KalkNum) -> KalkNum { - if self.has_imaginary() || rhs.has_imaginary() { - return KalkNum::from(f64::NAN); - } - - KalkNum::from_bool(self.value.clone() - rhs.value.clone() < -ACCEPTABLE_COMPARISON_MARGIN) - } - - pub fn get_conjugate(&self) -> KalkNum { - KalkNum::new_with_imaginary( - self.value.clone(), - &self.unit, - self.imaginary_value.clone() * (-1f64), - ) - } - /// Get an estimate of what the number is, eg. 3.141592 => π. Does not work properly with scientific notation. pub fn estimate(&self) -> Option { - let rounded_real = self.estimate_one_value(ComplexNumberType::Real); - let rounded_imaginary = self.estimate_one_value(ComplexNumberType::Imaginary); + let rounded_real = rounding::estimate(self, ComplexNumberType::Real); + let rounded_imaginary = rounding::estimate(self, ComplexNumberType::Imaginary); if let (None, None) = (&rounded_real, &rounded_imaginary) { return None; @@ -515,168 +386,394 @@ impl KalkNum { Some(output) } - fn estimate_one_value(&self, complex_number_type: ComplexNumberType) -> Option { - let (value, value_string) = match complex_number_type { - ComplexNumberType::Real => (&self.value, self.to_string()), - ComplexNumberType::Imaginary => { - (&self.imaginary_value, self.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 = KalkNum::new(value.clone() * value, "").round_if_needed(); - if squared.value.clone().sqrt().fract() != 0f64 && squared.value.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 => self.round_one_value(complex_number_type)?.value, - ComplexNumberType::Imaginary => { - self.round_one_value(complex_number_type)?.imaginary_value - } - }; - let rounded_str = rounded.to_string(); - Some(trim_zeroes(if rounded_str == "-0" { - "0" - } else { - &rounded_str - })) - } - /// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc. - pub fn round(&self) -> Option { - let rounded_real = self.round_one_value(ComplexNumberType::Real); - let rounded_imaginary = self.round_one_value(ComplexNumberType::Imaginary); + pub fn round(&self) -> Option { + let rounded_real = rounding::round(self, ComplexNumberType::Real); + let rounded_imaginary = rounding::round(self, ComplexNumberType::Imaginary); if let (None, None) = (&rounded_real, &rounded_imaginary) { return None; } - Some(KalkNum::new_with_imaginary( - if let Some(rounded) = rounded_real { - rounded.value + let (original_real, original_imaginary, unit) = match self { + KalkValue::Number(real, imaginary, unit) => (real, imaginary, unit), + _ => return None, + }; + + Some(KalkValue::Number( + if let Some(KalkValue::Number(real, _, _)) = rounded_real { + real } else { - self.value.clone() + original_real.clone() }, - &self.unit, - if let Some(rounded) = rounded_imaginary { - rounded.imaginary_value + if let Some(KalkValue::Number(_, imaginary, _)) = rounded_imaginary { + imaginary } else { - self.imaginary_value.clone() + original_imaginary.clone() }, + unit.to_string(), )) } - pub fn round_if_needed(&self) -> KalkNum { - if let Some(value) = self.round() { - value + pub fn round_if_needed(self) -> KalkValue { + if let Some(rounded) = self.round() { + rounded } else { - self.clone() // Hmm + self } } - fn round_one_value(&self, complex_number_type: ComplexNumberType) -> Option { - let value = match complex_number_type { - ComplexNumberType::Real => &self.value, - ComplexNumberType::Imaginary => &self.imaginary_value, - }; - 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) + pub fn has_real(&self) -> bool { + if let KalkValue::Number(real, _, _) = self { + real != &0f64 } else { - (-4f64, -6f64) + false + } + } + + pub fn has_imaginary(&self) -> bool { + if let KalkValue::Number(_, imaginary, _) = self { + imaginary != &0f64 + } else { + false + } + } + + pub fn to_scientific_notation( + &self, + complex_number_type: ComplexNumberType, + ) -> ScientificNotation { + let value = match complex_number_type { + ComplexNumberType::Real => self.to_f64(), + ComplexNumberType::Imaginary => self.imaginary_to_f64(), }; + let exponent = value.clone().abs().log10().floor() as i32 + 1; - if fract.clone().log10() < limit_floor { - // If eg. 0.00xxx - let mut new_num = self.clone(); - let new_value = integer * sign; - match complex_number_type { - ComplexNumberType::Real => new_num.value = new_value, - ComplexNumberType::Imaginary => new_num.imaginary_value = new_value, + ScientificNotation { + negative: value < 0f64, + value: value / (10f64.powf(exponent as f64 - 1f64) as f64), + // I... am not sure what else to do... + exponent, + imaginary: complex_number_type == ComplexNumberType::Imaginary, + } + } + + pub fn has_unit(&self) -> bool { + if let KalkValue::Number(_, _, unit) = self { + unit.len() > 0 + } else { + false + } + } + + pub fn get_unit(&self) -> String { + if let KalkValue::Number(_, _, unit) = self { + unit.to_string() + } else { + String::new() + } + } + + pub(crate) fn convert_to_unit( + &self, + context: &mut crate::interpreter::Context, + to_unit: &str, + ) -> Option { + if let KalkValue::Number(real, _, unit) = self { + let result = crate::interpreter::convert_unit( + context, + &Expr::Literal(primitive!(real)), + &unit, + to_unit, + ); + + if let Ok(num) = result { + Some(num) + } else { + None } - - 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 mut new_num = self.clone(); - let new_value = value.clone().abs().ceil() * sign; - match complex_number_type { - ComplexNumberType::Real => new_num.value = new_value, - ComplexNumberType::Imaginary => new_num.imaginary_value = new_value, - } - - Some(new_num) } else { None } } + + pub(crate) fn add( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.add_without_unit(right) + } + + pub(crate) fn sub( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.sub_without_unit(right) + } + + pub(crate) fn mul( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.mul_without_unit(right) + } + + pub(crate) fn div( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); + self.div_without_unit(right) + } + + pub(crate) fn rem( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + if let KalkValue::Number(real, _, _) = &self { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + if let KalkValue::Number(right_real, _, right_unit) = right { + KalkValue::Number(real % right_real, float!(0f64), right_unit) + } else { + self + } + } else { + self + } + } + + pub(crate) fn eq(self, context: &mut crate::interpreter::Context, rhs: KalkValue) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.eq_without_unit(&right) + } + + pub(crate) fn not_eq( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.not_eq_without_unit(&right) + } + + pub(crate) fn greater_than( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.greater_than_without_unit(&right) + } + + pub(crate) fn less_than( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.less_than_without_unit(&right) + } + + pub(crate) fn greater_or_equals( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); + if let (KalkValue::Boolean(greater), KalkValue::Boolean(equal)) = ( + self.greater_than_without_unit(&right), + self.eq_without_unit(&right), + ) { + KalkValue::Boolean(greater || equal) + } else { + unreachable!() + } + } + + pub(crate) fn less_or_equals( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs.clone()); + if let (KalkValue::Boolean(less), KalkValue::Boolean(equal)) = ( + self.less_than_without_unit(&right), + self.eq_without_unit(&right), + ) { + KalkValue::Boolean(less || equal) + } else { + unreachable!() + } + } + + pub(crate) fn add_without_unit(self, rhs: KalkValue) -> KalkValue { + match (self, rhs) { + ( + KalkValue::Number(real, imaginary, _), + KalkValue::Number(real_rhs, imaginary_rhs, unit), + ) => KalkValue::Number(real + real_rhs, imaginary + imaginary_rhs, unit), + _ => KalkValue::nan(), + } + } + + pub(crate) fn sub_without_unit(self, rhs: KalkValue) -> KalkValue { + match (self, rhs) { + ( + KalkValue::Number(real, imaginary, _), + KalkValue::Number(real_rhs, imaginary_rhs, unit), + ) => KalkValue::Number(real - real_rhs, imaginary - imaginary_rhs, unit), + _ => KalkValue::nan(), + } + } + + pub(crate) fn mul_without_unit(self, rhs: KalkValue) -> KalkValue { + // (a + bi)(c + di) = ac + adi + bci + bdi² + match (self, rhs) { + ( + KalkValue::Number(real, imaginary, _), + KalkValue::Number(real_rhs, imaginary_rhs, unit), + ) => KalkValue::Number( + real.clone() * real_rhs.clone() - imaginary.clone() * imaginary_rhs.clone(), + real * imaginary_rhs + imaginary * real_rhs, + unit, + ), + _ => KalkValue::nan(), + } + } + + pub(crate) fn div_without_unit(self, rhs: KalkValue) -> KalkValue { + match (self.clone(), rhs.clone()) { + ( + KalkValue::Number(real, imaginary, _), + KalkValue::Number(real_rhs, imaginary_rhs, unit), + ) => { + // Avoid unecessary calculations + if imaginary == 0f64 && imaginary_rhs == 0f64 { + KalkValue::Number(real / real_rhs, float!(0f64), unit) + } else { + // Multiply both the numerator and denominator + // with the conjugate of the denominator, and divide. + let conjugate = rhs.get_conjugate(); + let (numerator, numerator_imaginary) = + self.clone().mul_without_unit(conjugate.clone()).values(); + let (denominator, _) = rhs.clone().mul_without_unit(conjugate).values(); + KalkValue::Number( + numerator / denominator.clone(), + numerator_imaginary / denominator, + unit, + ) + } + } + _ => KalkValue::nan(), + } + } + + pub(crate) fn pow( + self, + context: &mut crate::interpreter::Context, + rhs: KalkValue, + ) -> KalkValue { + let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + self.pow_without_unit(right) + } + + pub(crate) fn pow_without_unit(self, rhs: KalkValue) -> KalkValue { + if let ( + KalkValue::Number(real, imaginary, _), + KalkValue::Number(real_rhs, imaginary_rhs, unit), + ) = (self.clone(), rhs) + { + if imaginary != 0f64 || imaginary_rhs != 0f64 || (real < 0f64 && real_rhs < 1f64) { + let a = real.clone(); + let b = imaginary.clone(); + let c = real_rhs; + let d = imaginary_rhs; + let arg = crate::prelude::funcs::arg(self).values().0; + let raised = a.clone() * a + b.clone() * b; + let exp = pow(raised.clone(), c.clone() / 2f64) * (-d.clone() * arg.clone()).exp(); + let polar = c * arg + d / 2f64 * raised.ln(); + + KalkValue::Number(polar.clone().cos() * exp.clone(), polar.sin() * exp, unit) + } else { + KalkValue::Number(pow(real, real_rhs), float!(0), unit) + } + } else { + KalkValue::nan() + } + } + + pub(crate) fn eq_without_unit(&self, rhs: &KalkValue) -> KalkValue { + if self.has_imaginary() || rhs.has_imaginary() { + return KalkValue::nan(); + } + + match (self, rhs) { + (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => { + KalkValue::Boolean( + (real.clone() - real_rhs.clone()).abs() < ACCEPTABLE_COMPARISON_MARGIN, + ) + } + _ => KalkValue::nan(), + } + } + + pub(crate) fn not_eq_without_unit(&self, rhs: &KalkValue) -> KalkValue { + if self.has_imaginary() || rhs.has_imaginary() { + return KalkValue::nan(); + } + + match (self, rhs) { + (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => { + KalkValue::Boolean( + (real.clone() - real_rhs.clone()).abs() > ACCEPTABLE_COMPARISON_MARGIN, + ) + } + _ => KalkValue::nan(), + } + } + + pub(crate) fn greater_than_without_unit(&self, rhs: &KalkValue) -> KalkValue { + if self.has_imaginary() || rhs.has_imaginary() { + return KalkValue::nan(); + } + + match (self, rhs) { + (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => { + KalkValue::Boolean(real.clone() - real_rhs.clone() > ACCEPTABLE_COMPARISON_MARGIN) + } + _ => KalkValue::nan(), + } + } + + pub(crate) fn less_than_without_unit(&self, rhs: &KalkValue) -> KalkValue { + if self.has_imaginary() || rhs.has_imaginary() { + return KalkValue::nan(); + } + + match (self, rhs) { + (KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => { + KalkValue::Boolean(real.clone() - real_rhs.clone() < ACCEPTABLE_COMPARISON_MARGIN) + } + _ => KalkValue::nan(), + } + } + + pub fn get_conjugate(&self) -> KalkValue { + match self { + KalkValue::Number(real, imaginary, unit) => { + KalkValue::Number(real.clone(), imaginary.clone() * (-1f64), unit.clone()) + } + _ => KalkValue::nan(), + } + } } pub fn format_number(input: f64) -> String { @@ -693,29 +790,35 @@ pub fn format_number(input: f64) -> String { fn calculate_unit( context: &mut crate::interpreter::Context, - left: &KalkNum, - right: KalkNum, -) -> Option { - if left.has_unit() && right.has_unit() { - right.convert_to_unit(context, &left.unit) + left: &KalkValue, + right: KalkValue, +) -> Option { + if let (KalkValue::Number(_, _, unit_left), KalkValue::Number(real_right, imaginary_right, _)) = + (left, &right) + { + if left.has_unit() && right.has_unit() { + right.convert_to_unit(context, unit_left) + } else { + Some(KalkValue::Number( + real_right.clone(), + imaginary_right.clone(), + unit_left.to_string(), + )) + } } else { - Some(KalkNum::new_with_imaginary( - right.value, - &left.unit, - right.imaginary_value, - )) + None } } -fn trim_zeroes(input: &str) -> String { - if input.contains(".") { - input - .trim_end_matches("0") - .trim_end_matches(".") - .to_string() - } else { - input.into() - } +#[cfg(not(feature = "rug"))] +fn pow(x: f64, y: f64) -> f64 { + x.powf(y) +} + +#[cfg(feature = "rug")] +fn pow(x: Float, y: Float) -> Float { + use rug::ops::Pow; + x.pow(y) } impl Into for ScientificNotation { @@ -724,21 +827,51 @@ impl Into for ScientificNotation { } } -impl Into for KalkNum { +impl Into for KalkValue { fn into(self) -> String { self.to_string() } } -impl Into for KalkNum { +impl Into for KalkValue { fn into(self) -> f64 { self.to_f64() } } +impl From for KalkValue { + fn from(x: f64) -> Self { + KalkValue::Number(float!(x), float!(0), String::new()) + } +} + +impl From for KalkValue { + fn from(x: f32) -> Self { + KalkValue::Number(float!(x), float!(0), String::new()) + } +} + +impl From for KalkValue { + fn from(x: i128) -> Self { + KalkValue::Number(float!(x), float!(0), String::new()) + } +} + +impl From for KalkValue { + fn from(x: i64) -> Self { + KalkValue::Number(float!(x), float!(0), String::new()) + } +} + +impl From for KalkValue { + fn from(x: i32) -> Self { + KalkValue::Number(float!(x), float!(0), String::new()) + } +} + #[cfg(test)] mod tests { - use crate::kalk_num::KalkNum; + use crate::kalk_value::KalkValue; use crate::test_helpers::cmp; #[test] @@ -751,13 +884,8 @@ mod tests { ]; for (a, b, expected_result) in in_out { - let actual_result = - KalkNum::new_with_imaginary(KalkNum::from(a.0).value, "", KalkNum::from(a.1).value) - .add_without_unit(KalkNum::new_with_imaginary( - KalkNum::from(b.0).value, - "", - KalkNum::from(b.1).value, - )); + let actual_result = KalkValue::Number(float!(a.0), float!(a.1), String::new()) + .add_without_unit(KalkValue::Number(float!(b.0), float!(b.1), String::new())); assert_eq!(actual_result.to_f64(), expected_result.0); assert_eq!(actual_result.imaginary_to_f64(), expected_result.1); } @@ -773,13 +901,8 @@ mod tests { ]; for (a, b, expected_result) in in_out { - let actual_result = - KalkNum::new_with_imaginary(KalkNum::from(a.0).value, "", KalkNum::from(a.1).value) - .sub_without_unit(KalkNum::new_with_imaginary( - KalkNum::from(b.0).value, - "", - KalkNum::from(b.1).value, - )); + let actual_result = KalkValue::Number(float!(a.0), float!(a.1), String::new()) + .sub_without_unit(KalkValue::Number(float!(b.0), float!(b.1), String::new())); assert_eq!(actual_result.to_f64(), expected_result.0); assert_eq!(actual_result.imaginary_to_f64(), expected_result.1); } @@ -795,13 +918,8 @@ mod tests { ]; for (a, b, expected_result) in in_out { - let actual_result = - KalkNum::new_with_imaginary(KalkNum::from(a.0).value, "", KalkNum::from(a.1).value) - .mul_without_unit(KalkNum::new_with_imaginary( - KalkNum::from(b.0).value, - "", - KalkNum::from(b.1).value, - )); + let actual_result = KalkValue::Number(float!(a.0), float!(a.1), String::new()) + .mul_without_unit(KalkValue::Number(float!(b.0), float!(b.1), String::new())); assert_eq!(actual_result.to_f64(), expected_result.0); assert_eq!(actual_result.imaginary_to_f64(), expected_result.1); } @@ -816,13 +934,8 @@ mod tests { ]; for (a, b, expected_result) in in_out { - let actual_result = - KalkNum::new_with_imaginary(KalkNum::from(a.0).value, "", KalkNum::from(a.1).value) - .div_without_unit(KalkNum::new_with_imaginary( - KalkNum::from(b.0).value, - "", - KalkNum::from(b.1).value, - )); + let actual_result = KalkValue::Number(float!(a.0), float!(a.1), String::new()) + .div_without_unit(KalkValue::Number(float!(b.0), float!(b.1), String::new())); assert_eq!(actual_result.to_f64(), expected_result.0); assert_eq!(actual_result.imaginary_to_f64(), expected_result.1); } @@ -849,13 +962,8 @@ mod tests { ]; for (a, b, expected_result) in in_out { - let actual_result = - KalkNum::new_with_imaginary(KalkNum::from(a.0).value, "", KalkNum::from(a.1).value) - .pow_without_unit(KalkNum::new_with_imaginary( - KalkNum::from(b.0).value, - "", - KalkNum::from(b.1).value, - )); + let actual_result = KalkValue::Number(float!(a.0), float!(a.1), String::new()) + .pow_without_unit(KalkValue::Number(float!(b.0), float!(b.1), String::new())); assert!(cmp(actual_result.to_f64(), expected_result.0)); assert!(cmp(actual_result.imaginary_to_f64(), expected_result.1)); } @@ -888,12 +996,8 @@ mod tests { (3.00000000004, 0.0, "3"), ]; for (real, imaginary, output) in in_out { - let result = KalkNum::new_with_imaginary( - KalkNum::from(real).value, - "", - KalkNum::from(imaginary).value, - ) - .to_string_pretty(); + let result = KalkValue::Number(float!(real), float!(imaginary), String::new()) + .to_string_pretty(); assert_eq!(output, result); } } @@ -966,7 +1070,7 @@ mod tests { ]; for (input, output) in in_out { - let result = KalkNum::from(input).estimate(); + let result = KalkValue::from(input).estimate(); println!("{}", input); assert_eq!(output, result); } diff --git a/kalk/src/kalk_value/regular.rs b/kalk/src/kalk_value/regular.rs new file mode 100644 index 0000000..c30188d --- /dev/null +++ b/kalk/src/kalk_value/regular.rs @@ -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) + } + } +} diff --git a/kalk/src/kalk_value/rounding.rs b/kalk/src/kalk_value/rounding.rs new file mode 100644 index 0000000..440c08a --- /dev/null +++ b/kalk/src/kalk_value/rounding.rs @@ -0,0 +1,166 @@ +use crate::float; + +use super::{ComplexNumberType, KalkValue, CONSTANTS}; + +pub(super) fn estimate( + input: &KalkValue, + complex_number_type: ComplexNumberType, +) -> Option { + 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 { + 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() + } +} diff --git a/kalk/src/kalk_value/with_rug.rs b/kalk/src/kalk_value/with_rug.rs new file mode 100644 index 0000000..1274fef --- /dev/null +++ b/kalk/src/kalk_value/with_rug.rs @@ -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)) + } + } +} diff --git a/kalk/src/lib.rs b/kalk/src/lib.rs index fa3c9f4..a9dfbc3 100644 --- a/kalk/src/lib.rs +++ b/kalk/src/lib.rs @@ -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; diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 9e4a18c..8a6e805 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -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, JsValue> { + pub fn js_eval(&mut self, input: &str) -> Result, 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, CalcError> { +) -> Result, 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 diff --git a/kalk/src/prelude/mod.rs b/kalk/src/prelude/mod.rs index 161599e..66f2cd5 100644 --- a/kalk/src/prelude/mod.rs +++ b/kalk/src/prelude/mod.rs @@ -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() diff --git a/kalk/src/prelude/regular.rs b/kalk/src/prelude/regular.rs index 0bccdee..c545501 100644 --- a/kalk/src/prelude/regular.rs +++ b/kalk/src/prelude/regular.rs @@ -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) } } diff --git a/kalk/src/prelude/with_rug.rs b/kalk/src/prelude/with_rug.rs index acf36d0..9df2dd3 100644 --- a/kalk/src/prelude/with_rug.rs +++ b/kalk/src/prelude/with_rug.rs @@ -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) } } diff --git a/kalk/src/radix.rs b/kalk/src/radix.rs index c099c65..00201ad 100644 --- a/kalk/src/radix.rs +++ b/kalk/src/radix.rs @@ -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!( "{}{}",