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()),
));
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 {

View File

@ -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 {
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<f64> 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);
}

View File

@ -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,
}
}

View File

@ -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<f64> for KalkNum {
fn from(x: f64) -> Self {
KalkNum::new(Float::with_val(63, x), "")

View File

@ -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! {

View File

@ -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))
}

View File

@ -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()),
}