Basics of complex numbers

This commit is contained in:
= 2021-05-20 15:11:32 +02:00
parent 869708b454
commit 2936a58620
7 changed files with 209 additions and 44 deletions

View File

@ -212,9 +212,11 @@ pub fn convert_unit(
Box::new(expr.clone()), Box::new(expr.clone()),
)); ));
Ok(KalkNum::new( let num = eval_expr(context, &unit_def, "")?;
eval_expr(context, &unit_def, "")?.value, Ok(KalkNum::new_with_imaginary(
num.value,
to_unit.into(), to_unit.into(),
num.imaginary_value,
)) ))
} else { } else {
Err(CalcError::InvalidUnit) Err(CalcError::InvalidUnit)
@ -273,6 +275,23 @@ pub(crate) fn eval_fn_call_expr(
let prelude_func = match expressions.len() { let prelude_func = match expressions.len() {
1 => { 1 => {
let x = eval_expr(context, &expressions[0], "")?; 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 { if identifier.prime_count > 0 {
return calculus::derive_func(context, &identifier, x); return calculus::derive_func(context, &identifier, x);
} else { } else {

View File

@ -54,12 +54,24 @@ pub struct ScientificNotation {
pub negative: bool, pub negative: bool,
pub(crate) digits: String, pub(crate) digits: String,
pub exponent: i32, pub exponent: i32,
pub imaginary: bool,
}
#[wasm_bindgen]
#[derive(PartialEq)]
pub enum ComplexNumberType {
Real,
Imaginary,
} }
#[wasm_bindgen] #[wasm_bindgen]
impl ScientificNotation { impl ScientificNotation {
#[wasm_bindgen(js_name = toString)] #[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
if self.digits == "" {
return String::from("0");
}
let sign = if self.negative { "-" } else { "" }; let sign = if self.negative { "-" } else { "" };
let mut digits_and_mul = if self.digits == "1" { let mut digits_and_mul = if self.digits == "1" {
String::new() String::new()
@ -71,33 +83,106 @@ impl ScientificNotation {
digits_and_mul.insert(1usize, '.'); 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 { impl KalkNum {
pub fn to_scientific_notation(&self) -> ScientificNotation { pub fn to_scientific_notation(
let value_string = self.to_string(); &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(".") { let trimmed = if value_string.contains(".") {
value_string.trim_end_matches("0") value_string.trim_end_matches("0")
} else { } else {
&value_string &value_string
}; };
let value = match complex_number_type {
ComplexNumberType::Real => self.value.clone(),
ComplexNumberType::Imaginary => self.imaginary_value.clone(),
};
ScientificNotation { ScientificNotation {
negative: self.value < 0f64, negative: value < 0f64,
digits: trimmed digits: trimmed
.to_string() .to_string()
.replace(".", "") .replace(".", "")
.trim_start_matches("0") .trim_start_matches("0")
.to_string(), .to_string(),
// I... am not sure what else to do... // 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 { pub fn to_string_big(&self) -> String {
if self.imaginary_value == 0 {
self.value.to_string() 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 { 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 { pub(crate) fn add(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); 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 { pub(crate) fn sub(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); 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 { pub(crate) fn mul(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); 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 { 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() { if left.has_unit() && right.has_unit() {
right.convert_to_unit(context, &left.unit) right.convert_to_unit(context, &left.unit)
} else { } 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<f64> for KalkNum {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::kalk_num::ComplexNumberType;
use crate::kalk_num::KalkNum; use crate::kalk_num::KalkNum;
#[test] #[test]
@ -372,12 +474,12 @@ mod tests {
#[test] #[test]
fn test_to_scientific_notation() { fn test_to_scientific_notation() {
let num = KalkNum::from(0.000001f64); 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.negative, false);
assert_eq!(sci_not.exponent, -6); assert_eq!(sci_not.exponent, -6);
let num = KalkNum::from(123.456789f64); 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.negative, false);
assert_eq!(sci_not.exponent, 2); assert_eq!(sci_not.exponent, 2);
} }

View File

@ -6,6 +6,7 @@ use wasm_bindgen::prelude::*;
pub struct KalkNum { pub struct KalkNum {
pub(crate) value: f64, pub(crate) value: f64,
pub(crate) unit: String, pub(crate) unit: String,
pub(crate) imaginary_value: f64,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -14,6 +15,15 @@ impl KalkNum {
Self { Self {
value, value,
unit: unit.to_string(), 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,
} }
} }

View File

@ -10,6 +10,7 @@ impl Default for KalkNum {
pub struct KalkNum { pub struct KalkNum {
pub(crate) value: Float, pub(crate) value: Float,
pub(crate) unit: String, pub(crate) unit: String,
pub(crate) imaginary_value: Float,
} }
impl KalkNum { impl KalkNum {
@ -17,6 +18,15 @@ impl KalkNum {
Self { Self {
value, value,
unit: unit.to_string(), 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) 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 { pub fn to_i32(&self) -> i32 {
self.value.to_i32_saturating().unwrap() self.value.to_i32_saturating().unwrap()
} }
pub fn to_string(&self) -> String { 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(".") { if self.imaginary_value != 0 {
as_str let imaginary_as_str = trim_number_string(&self.imaginary_to_f64().to_string());
.trim_end_matches('0') let sign = if self.imaginary_value < 0 { "-" } else { "+" };
.trim_end_matches('.')
.to_string() format!("{} {} {}i", as_str, sign, imaginary_as_str)
} else { } else {
as_str 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 { pub fn get_unit(&self) -> &str {
&self.unit &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<f64> for KalkNum { impl From<f64> for KalkNum {
fn from(x: f64) -> Self { fn from(x: f64) -> Self {
KalkNum::new(Float::with_val(63, x), "") KalkNum::new(Float::with_val(63, x), "")

View File

@ -2,6 +2,7 @@ use lazy_static::lazy_static;
use std::collections::HashMap; use std::collections::HashMap;
use FuncType::*; 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"; pub const INIT: &'static str = "unit deg = (rad*180)/pi";
lazy_static! { lazy_static! {

View File

@ -1,4 +1,4 @@
use crate::{ast::Stmt, prelude}; use crate::{ast::Expr, ast::Identifier, ast::Stmt, prelude};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug)] #[derive(Debug)]
@ -9,10 +9,21 @@ pub struct SymbolTable {
impl SymbolTable { impl SymbolTable {
pub fn new() -> Self { pub fn new() -> Self {
SymbolTable { let mut symbol_table = SymbolTable {
hashmap: HashMap::new(), hashmap: HashMap::new(),
unit_types: 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 { pub fn insert(&mut self, value: Stmt) -> &mut Self {
@ -72,6 +83,7 @@ impl SymbolTable {
pub fn contains_var(&self, identifier: &str) -> bool { pub fn contains_var(&self, identifier: &str) -> bool {
prelude::CONSTANTS.contains_key(identifier) prelude::CONSTANTS.contains_key(identifier)
|| identifier == "i"
|| self.hashmap.contains_key(&format!("var.{}", identifier)) || self.hashmap.contains_key(&format!("var.{}", identifier))
} }

View File

@ -4,27 +4,7 @@ use kalk::parser;
pub fn eval(parser: &mut parser::Context, input: &str, precision: u32) { pub fn eval(parser: &mut parser::Context, input: &str, precision: u32) {
match parser::eval(parser, input, precision) { match parser::eval(parser, input, precision) {
Ok(Some(result)) => { Ok(Some(result)) => println!("{}", result.to_string_pretty()),
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(None) => print!(""), Ok(None) => print!(""),
Err(err) => print_err(&err.to_string()), Err(err) => print_err(&err.to_string()),
} }