From 2936a5862078ad3d90faf5074c64a376c6348f05 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 20 May 2021 15:11:32 +0200 Subject: [PATCH] Basics of complex numbers --- kalk/src/interpreter.rs | 23 ++++++- kalk/src/kalk_num/mod.rs | 126 ++++++++++++++++++++++++++++++---- kalk/src/kalk_num/regular.rs | 10 +++ kalk/src/kalk_num/with_rug.rs | 53 ++++++++++++-- kalk/src/prelude/mod.rs | 1 + kalk/src/symbol_table.rs | 18 ++++- kalk_cli/src/output.rs | 22 +----- 7 files changed, 209 insertions(+), 44 deletions(-) diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index b918b70..4e28d8a 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -212,9 +212,11 @@ pub fn convert_unit( Box::new(expr.clone()), )); - Ok(KalkNum::new( - eval_expr(context, &unit_def, "")?.value, + let num = eval_expr(context, &unit_def, "")?; + Ok(KalkNum::new_with_imaginary( + num.value, to_unit.into(), + num.imaginary_value, )) } else { Err(CalcError::InvalidUnit) @@ -273,6 +275,23 @@ pub(crate) fn eval_fn_call_expr( let prelude_func = match expressions.len() { 1 => { let x = eval_expr(context, &expressions[0], "")?; + + if x.value < 0f64 && (identifier.full_name == "sqrt" || identifier.full_name == "√") { + let (sqrt, unit) = prelude::call_unary_func( + context, + &identifier.full_name, + x.value * (-1f64), + &context.angle_unit.clone(), + ) + .unwrap(); + + return Ok(KalkNum::new_with_imaginary( + KalkNum::default().value, + &unit, + sqrt, + )); + } + if identifier.prime_count > 0 { return calculus::derive_func(context, &identifier, x); } else { diff --git a/kalk/src/kalk_num/mod.rs b/kalk/src/kalk_num/mod.rs index f134cf5..2c604ef 100644 --- a/kalk/src/kalk_num/mod.rs +++ b/kalk/src/kalk_num/mod.rs @@ -54,12 +54,24 @@ pub struct ScientificNotation { pub negative: bool, pub(crate) digits: String, pub exponent: i32, + pub imaginary: bool, +} + +#[wasm_bindgen] +#[derive(PartialEq)] +pub enum ComplexNumberType { + Real, + Imaginary, } #[wasm_bindgen] impl ScientificNotation { #[wasm_bindgen(js_name = toString)] pub fn to_string(&self) -> String { + if self.digits == "" { + return String::from("0"); + } + let sign = if self.negative { "-" } else { "" }; let mut digits_and_mul = if self.digits == "1" { String::new() @@ -71,33 +83,106 @@ impl ScientificNotation { digits_and_mul.insert(1usize, '.'); } - format!("{}{}10^{}", sign, digits_and_mul, self.exponent - 1) + format!( + "{}{}10^{} {}", + sign, + digits_and_mul, + self.exponent - 1, + if self.imaginary { "i" } else { "" } + ) } } impl KalkNum { - pub fn to_scientific_notation(&self) -> ScientificNotation { - let value_string = self.to_string(); + pub fn to_scientific_notation( + &self, + complex_number_type: ComplexNumberType, + ) -> ScientificNotation { + let value_string = match complex_number_type { + ComplexNumberType::Real => self.to_string_real(), + ComplexNumberType::Imaginary => self.to_string_imaginary(false), + }; let trimmed = if value_string.contains(".") { value_string.trim_end_matches("0") } else { &value_string }; + let value = match complex_number_type { + ComplexNumberType::Real => self.value.clone(), + ComplexNumberType::Imaginary => self.imaginary_value.clone(), + }; ScientificNotation { - negative: self.value < 0f64, + negative: value < 0f64, digits: trimmed .to_string() .replace(".", "") .trim_start_matches("0") .to_string(), // I... am not sure what else to do... - exponent: KalkNum::new(self.value.clone().log10(), "").to_i32(), + exponent: KalkNum::new(value.clone().abs().log10(), "").to_i32(), + imaginary: complex_number_type == ComplexNumberType::Imaginary, } } pub fn to_string_big(&self) -> String { - self.value.to_string() + if self.imaginary_value == 0 { + self.value.to_string() + } else { + let sign = if self.imaginary_value < 0 { "-" } else { "+" }; + format!( + "{} {} {}", + self.value.to_string(), + sign, + self.imaginary_value.to_string() + ) + } + } + + pub fn to_string_pretty(&self) -> String { + let sci_notation_real = self.to_scientific_notation(ComplexNumberType::Real); + let result_str = if (-6..8).contains(&sci_notation_real.exponent) || self.value == 0f64 { + self.to_string_real() + } else { + sci_notation_real.to_string() + }; + + 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 + { + self.to_string_imaginary(true) + } else { + format!("{} i", sci_notation_imaginary.to_string()) + }; + + let mut output = result_str; + if self.imaginary_value != 0f64 { + // 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" { + output = String::new(); + } + if output.len() > 0 { + output.push_str(&format!( + " {} ", + if self.imaginary_value < 0 { "-" } else { "+" } + )); + } + output.push_str(&format!("{}", result_str_imaginary)); + } + + let unit = self.get_unit(); + if unit != "" { + output.push_str(&format!(" {}", unit)); + } + + if let Some(estimate) = self.estimate() { + output.push_str(&format!(" ≈ {}", estimate)); + } + + output } pub fn is_too_big(&self) -> bool { @@ -133,17 +218,29 @@ impl KalkNum { pub(crate) fn add(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) + KalkNum::new_with_imaginary( + self.value + right.value, + &right.unit, + self.imaginary_value + right.imaginary_value, + ) } pub(crate) fn sub(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) + KalkNum::new_with_imaginary( + self.value - right.value, + &right.unit, + self.imaginary_value - right.imaginary_value, + ) } pub(crate) fn mul(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) + KalkNum::new_with_imaginary( + KalkNum::default().value, + &right.unit, + self.value * right.imaginary_value + self.imaginary_value * right.value, + ) } pub(crate) fn div(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { @@ -258,7 +355,11 @@ fn calculate_unit( if left.has_unit() && right.has_unit() { right.convert_to_unit(context, &left.unit) } else { - Some(KalkNum::new(right.value, &left.unit)) + Some(KalkNum::new_with_imaginary( + right.value, + &left.unit, + right.imaginary_value, + )) } } @@ -293,6 +394,7 @@ impl Into for KalkNum { #[cfg(test)] mod tests { + use crate::kalk_num::ComplexNumberType; use crate::kalk_num::KalkNum; #[test] @@ -372,12 +474,12 @@ mod tests { #[test] fn test_to_scientific_notation() { let num = KalkNum::from(0.000001f64); - let sci_not = num.to_scientific_notation(); + let sci_not = num.to_scientific_notation(ComplexNumberType::Real); assert_eq!(sci_not.negative, false); assert_eq!(sci_not.exponent, -6); let num = KalkNum::from(123.456789f64); - let sci_not = num.to_scientific_notation(); + let sci_not = num.to_scientific_notation(ComplexNumberType::Real); assert_eq!(sci_not.negative, false); assert_eq!(sci_not.exponent, 2); } diff --git a/kalk/src/kalk_num/regular.rs b/kalk/src/kalk_num/regular.rs index 8660f3c..fcabaaa 100644 --- a/kalk/src/kalk_num/regular.rs +++ b/kalk/src/kalk_num/regular.rs @@ -6,6 +6,7 @@ use wasm_bindgen::prelude::*; pub struct KalkNum { pub(crate) value: f64, pub(crate) unit: String, + pub(crate) imaginary_value: f64, } #[wasm_bindgen] @@ -14,6 +15,15 @@ impl KalkNum { Self { value, unit: unit.to_string(), + imaginary_value: 0f64, + } + } + + pub fn new_with_imaginary(value: f64, unit: &str, imaginary_value: f64) -> Self { + Self { + value, + unit: unit.to_string(), + imaginary_value, } } diff --git a/kalk/src/kalk_num/with_rug.rs b/kalk/src/kalk_num/with_rug.rs index 0ccf659..8fbc307 100644 --- a/kalk/src/kalk_num/with_rug.rs +++ b/kalk/src/kalk_num/with_rug.rs @@ -10,6 +10,7 @@ impl Default for KalkNum { pub struct KalkNum { pub(crate) value: Float, pub(crate) unit: String, + pub(crate) imaginary_value: Float, } impl KalkNum { @@ -17,6 +18,15 @@ impl KalkNum { Self { value, unit: unit.to_string(), + imaginary_value: Float::with_val(63, 0), + } + } + + pub fn new_with_imaginary(value: Float, unit: &str, imaginary_value: Float) -> Self { + Self { + value, + unit: unit.to_string(), + imaginary_value, } } @@ -24,23 +34,43 @@ impl KalkNum { 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() } pub fn to_string(&self) -> String { - let as_str = self.to_f64().to_string(); + let as_str = trim_number_string(&self.to_f64().to_string()); - if as_str.contains(".") { - as_str - .trim_end_matches('0') - .trim_end_matches('.') - .to_string() + if self.imaginary_value != 0 { + let imaginary_as_str = trim_number_string(&self.imaginary_to_f64().to_string()); + let sign = if self.imaginary_value < 0 { "-" } else { "+" }; + + format!("{} {} {}i", as_str, sign, imaginary_as_str) } else { as_str } } + pub fn to_string_real(&self) -> String { + trim_number_string(&self.to_f64().to_string()) + } + + pub fn to_string_imaginary(&self, include_i: bool) -> String { + let value = trim_number_string(&self.imaginary_to_f64().to_string()); + if include_i && value == "1" { + String::from("i") + } else if include_i { + format!("{}i", value) + } else { + value + } + } + pub fn get_unit(&self) -> &str { &self.unit } @@ -51,6 +81,17 @@ impl KalkNum { } } +fn trim_number_string(input: &str) -> String { + if input.contains(".") { + input + .trim_end_matches('0') + .trim_end_matches('.') + .to_string() + } else { + input.into() + } +} + impl From for KalkNum { fn from(x: f64) -> Self { KalkNum::new(Float::with_val(63, x), "") diff --git a/kalk/src/prelude/mod.rs b/kalk/src/prelude/mod.rs index 78ae95f..4aa7e94 100644 --- a/kalk/src/prelude/mod.rs +++ b/kalk/src/prelude/mod.rs @@ -2,6 +2,7 @@ use lazy_static::lazy_static; use std::collections::HashMap; use FuncType::*; +// `i` is added in the symbol_table module, since for some reason it didn't work here. pub const INIT: &'static str = "unit deg = (rad*180)/pi"; lazy_static! { diff --git a/kalk/src/symbol_table.rs b/kalk/src/symbol_table.rs index 2723ee8..784093a 100644 --- a/kalk/src/symbol_table.rs +++ b/kalk/src/symbol_table.rs @@ -1,4 +1,4 @@ -use crate::{ast::Stmt, prelude}; +use crate::{ast::Expr, ast::Identifier, ast::Stmt, prelude}; use std::collections::HashMap; #[derive(Debug)] @@ -9,10 +9,21 @@ pub struct SymbolTable { impl SymbolTable { pub fn new() -> Self { - SymbolTable { + let mut symbol_table = SymbolTable { hashmap: HashMap::new(), unit_types: HashMap::new(), - } + }; + + // i = sqrt(-1) + symbol_table.insert(Stmt::VarDecl( + Identifier::from_full_name("i"), + Box::new(Expr::FnCall( + Identifier::from_full_name("sqrt"), + vec![Expr::Literal(-1f64)], + )), + )); + + symbol_table } pub fn insert(&mut self, value: Stmt) -> &mut Self { @@ -72,6 +83,7 @@ impl SymbolTable { pub fn contains_var(&self, identifier: &str) -> bool { prelude::CONSTANTS.contains_key(identifier) + || identifier == "i" || self.hashmap.contains_key(&format!("var.{}", identifier)) } diff --git a/kalk_cli/src/output.rs b/kalk_cli/src/output.rs index c439755..6dc7cb7 100644 --- a/kalk_cli/src/output.rs +++ b/kalk_cli/src/output.rs @@ -4,27 +4,7 @@ use kalk::parser; pub fn eval(parser: &mut parser::Context, input: &str, precision: u32) { match parser::eval(parser, input, precision) { - Ok(Some(result)) => { - let sci_notation = result.to_scientific_notation(); - let result_str = if sci_notation.exponent > 8 || sci_notation.exponent < -6 { - sci_notation.to_string() - } else if precision == DEFAULT_PRECISION { - result.to_string() - } else { - result.to_string_big() - }; - - let unit = result.get_unit(); - if let Some(estimate) = result.estimate() { - if unit == "" { - println!("{} ≈ {}", result_str, estimate); - } else { - println!("{} {} ≈ {}", result_str, unit, estimate); - } - } else { - println!("{} {}", result_str, unit); - } - } + Ok(Some(result)) => println!("{}", result.to_string_pretty()), Ok(None) => print!(""), Err(err) => print_err(&err.to_string()), }