Files
kalker/kalk/src/kalk_value/mod.rs

1536 lines
50 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#[cfg(feature = "rug")]
pub mod with_rug;
#[cfg(feature = "rug")]
use rug::Float;
#[cfg(feature = "rug")]
pub use with_rug::*;
#[cfg(not(feature = "rug"))]
pub mod regular;
#[cfg(not(feature = "rug"))]
pub use regular::*;
mod rounding;
use crate::ast::Expr;
use crate::errors::KalkError;
use crate::radix;
use wasm_bindgen::prelude::*;
const ACCEPTABLE_COMPARISON_MARGIN: f64 = 0.00000001;
#[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 == -0f64 {
float!(0)
} else {
imaginary
},
unit,
)
} else {
return Err(KalkError::UnexpectedType(
$x.get_type_name(),
vec![String::from("number")],
));
}
}};
}
#[macro_export]
macro_rules! as_vector_or_return {
($x:expr) => {{
if let KalkValue::Vector(values) = $x {
if values.len() == 0 {
return Err(KalkError::Expected(String::from("a non-empty vector")));
}
values
} else {
return Err(KalkError::UnexpectedType(
$x.get_type_name(),
vec![String::from("vector")],
));
}
}};
}
#[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), None)
}
}};
}
#[wasm_bindgen]
#[derive(Clone)]
pub struct ScientificNotation {
pub negative: bool,
pub value: f64,
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_js_string(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for ScientificNotation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sign = if self.negative { "-" } else { "" };
let digits_and_mul = if self.value == 1f64 {
String::new()
} else {
format!("{}×", format_number(self.value))
};
write!(
f,
"{}{}10^{} {}",
sign,
digits_and_mul,
self.exponent - 1,
if self.imaginary { "i" } else { "" }
)
}
}
#[derive(PartialEq, Debug, Clone)]
pub enum KalkValue {
#[cfg(not(feature = "rug"))]
Number(f64, f64, Option<String>),
#[cfg(feature = "rug")]
Number(Float, Float, Option<String>),
Boolean(bool),
Vector(Vec<KalkValue>),
Matrix(Vec<Vec<KalkValue>>),
}
impl std::fmt::Display for KalkValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KalkValue::Number(real, imaginary, _) => {
let as_str = format_number(primitive!(real));
if self.has_imaginary() {
let imaginary_as_str = format_number(primitive!(imaginary).abs());
let sign = if imaginary < &0f64 { "-" } else { "+" };
if &as_str == "0" {
write!(f, "{}", imaginary_as_str)
} else {
write!(f, "{} {} {}i", as_str, sign, imaginary_as_str)
}
} else {
write!(f, "{}", as_str)
}
}
KalkValue::Boolean(is_true) => {
if *is_true {
write!(f, "true")
} else {
write!(f, "false")
}
}
KalkValue::Vector(values) => {
write!(
f,
"({})",
values
.iter()
.map(|x| x.estimate().unwrap_or_else(|| x.to_string()))
.collect::<Vec<String>>()
.join(", ")
)
}
KalkValue::Matrix(rows) => {
let mut value_strings = Vec::new();
let mut longest = 0;
for row in rows {
for value in row {
let value_str = value.estimate().unwrap_or_else(|| value.to_string());
longest = longest.max(value_str.len());
value_strings.push(format!("{},", value_str));
}
value_strings.last_mut().unwrap().pop(); // Trailing comma
value_strings.push(String::from("\n"));
}
let mut result = String::from("[");
for value_str in value_strings {
if value_str == "\n" {
result.push_str("\n ");
} else {
result.push_str(&format!("{:width$} ", value_str, width = longest + 1));
}
}
result.pop(); // Trailing new-line
result.pop(); // Trailing space
result.pop(); // Trailing space
result.pop(); // Trailing comma
result.push(']');
write!(f, "{}", result)
}
}
}
}
impl KalkValue {
pub fn nan() -> Self {
KalkValue::Number(float!(f64::NAN), float!(0f64), None)
}
pub fn get_type_name(&self) -> String {
match self {
KalkValue::Number(_, _, _) => String::from("number"),
KalkValue::Boolean(_) => String::from("boolean"),
KalkValue::Vector(_) => String::from("vector"),
KalkValue::Matrix(_) => String::from("matrix"),
}
}
pub fn to_string_big(&self) -> String {
fn trim_num(num_str: String) -> String {
num_str
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}
if let KalkValue::Number(real, imaginary, _) = self {
if !self.has_imaginary() {
return trim_num(real.to_string());
}
let sign = if imaginary < &0f64 { "-" } else { "+" };
format!(
"{} {} {}i",
spaced(&trim_num(real.to_string())),
sign,
spaced(&trim_num(imaginary.to_string()))
)
} else {
trim_num(self.to_string())
}
}
pub fn to_string_real(&self, radix: u8) -> String {
radix::to_radix_pretty(self.to_f64(), radix)
}
pub fn to_string_imaginary(&self, radix: u8, include_i: bool) -> String {
let value = radix::to_radix_pretty(self.imaginary_to_f64(), radix);
if include_i && value == "1" {
String::from("i")
} else if include_i && value == "-1" {
String::from("-i")
} else if include_i {
format!("{}i", value)
} else {
value
}
}
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();
if real_f64.is_nan() || imaginary_f64.is_nan() {
return String::from("Not defined.");
}
if real_f64.is_infinite() {
return format!("{}", if real_f64.is_sign_negative() { "-" } else { "" });
}
let sci_notation_real = self.to_scientific_notation(ComplexNumberType::Real);
let mut new_real = real.clone();
let mut new_imaginary = imaginary.clone();
let mut has_scientific_notation = false;
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 {
new_real = float!(0);
String::from("0")
} else if radix == 10 {
has_scientific_notation = true;
sci_notation_real.to_string().trim().to_string()
} else {
return String::new();
};
let sci_notation_imaginary = self.to_scientific_notation(ComplexNumberType::Imaginary);
let result_str_imaginary = if (-6..8).contains(&sci_notation_imaginary.exponent)
|| imaginary == &0f64
|| imaginary == &1f64
{
self.to_string_imaginary(radix, true)
} else if sci_notation_imaginary.exponent <= -14 {
new_imaginary = float!(0);
String::from("0")
} else if radix == 10 {
has_scientific_notation = true;
sci_notation_imaginary.to_string().trim().to_string()
} else {
return String::new();
};
let mut output = result_str;
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" {
output = String::new();
}
// If there is a real value as well
if !output.is_empty() {
output.push_str(&format!(
" {} {}",
if imaginary < &0f64 { "-" } else { "+" },
result_str_imaginary.trim_start_matches('-'),
));
} else {
output.push_str(&result_str_imaginary);
}
}
if let Some(unit) = unit {
output.push_str(&format!(" {}", unit));
}
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));
}
} else if has_scientific_notation {
output.insert_str(0, &format!("{}", self));
}
output
}
pub fn to_string_pretty(&self) -> String {
self.to_string_pretty_radix(10)
}
pub fn to_string_with_unit(&self) -> String {
match self {
KalkValue::Number(_, _, unit) => {
format!("{} {}", self, unit.as_ref().unwrap_or(&String::new()))
}
_ => self.to_string(),
}
}
/// Get an estimate of what the number is, eg. 3.141592 => π. Does not work properly with scientific notation.
pub fn estimate(&self) -> Option<String> {
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;
}
let mut output = String::new();
if let Some(value) = rounded_real {
output.push_str(&value);
} else if self.has_real() {
output.push_str(&self.to_string_real(10));
}
let imaginary_value = if let Some(value) = rounded_imaginary {
Some(value)
} else if self.has_imaginary() {
Some(self.to_string_imaginary(10, false))
} else {
None
};
if let Some(value) = imaginary_value {
// Clear output if it's just 0.
if output == "0" {
output = String::new();
}
if value == "0" {
// If both values ended up being estimated as zero,
// return zero.
if output.is_empty() {
return Some(String::from("0"));
}
} else {
let sign = if value.starts_with('-') { "-" } else { "+" };
let value = match value.as_ref() {
"1" => String::from("i"),
"-1" => String::from("-i"),
_ => format!("{}i", value),
};
// If there is a real value as well
if !output.is_empty() {
output.push_str(&format!(" {} {}", sign, value.trim_start_matches('-')));
} else {
output.push_str(&value);
}
}
}
Some(output)
}
/// Basic up/down rounding from 0.00xxx or 0.999xxx or xx.000xxx, etc.
pub fn round(&self) -> Option<KalkValue> {
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;
}
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 {
original_real.clone()
},
if let Some(KalkValue::Number(_, imaginary, _)) = rounded_imaginary {
imaginary
} else {
original_imaginary.clone()
},
unit.clone(),
))
}
pub fn round_if_needed(self) -> KalkValue {
if let Some(rounded) = self.round() {
rounded
} else {
self
}
}
pub fn has_real(&self) -> bool {
if let KalkValue::Number(real, _, _) = self {
real != &0f64
} else {
false
}
}
pub fn has_imaginary(&self) -> bool {
if let KalkValue::Number(_, imaginary, _) = self {
imaginary != &0f64 && imaginary != &-0f64
} else {
false
}
}
pub fn is_nan(&self) -> bool {
if let KalkValue::Number(real, imaginary, _) = self {
real.is_nan() || imaginary.is_nan()
} 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.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,
}
}
pub fn has_unit(&self) -> bool {
if let KalkValue::Number(_, _, unit) = self {
unit.is_some()
} else {
false
}
}
pub fn get_unit(&self) -> Option<&String> {
if let KalkValue::Number(_, _, unit) = self {
unit.as_ref()
} else {
None
}
}
pub(crate) fn convert_to_unit(
&self,
context: &mut crate::interpreter::Context,
to_unit: &str,
) -> Option<KalkValue> {
if let KalkValue::Number(real, _, unit) = self {
let result = crate::interpreter::convert_unit(
context,
&Expr::Literal(primitive!(real)),
unit.as_ref(),
Some(&to_unit.to_string()),
);
if let Ok(num) = result {
Some(num)
} else {
None
}
} else {
None
}
}
pub(crate) fn add(
self,
context: &mut crate::interpreter::Context,
rhs: KalkValue,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
self.div_without_unit(&right)
}
pub(crate) fn pow(
self,
context: &mut crate::interpreter::Context,
rhs: KalkValue,
) -> Result<KalkValue, KalkError> {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
self.pow_without_unit(&right)
}
pub(crate) fn rem(
self,
context: &mut crate::interpreter::Context,
rhs: KalkValue,
) -> Result<KalkValue, KalkError> {
Ok(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,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
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,
) -> Result<KalkValue, KalkError> {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
if let (KalkValue::Boolean(greater), KalkValue::Boolean(equal)) = (
self.greater_than_without_unit(&right)?,
self.eq_without_unit(&right)?,
) {
Ok(KalkValue::Boolean(greater || equal))
} else {
unreachable!()
}
}
pub(crate) fn less_or_equals(
self,
context: &mut crate::interpreter::Context,
rhs: KalkValue,
) -> Result<KalkValue, KalkError> {
let right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
if let (KalkValue::Boolean(less), KalkValue::Boolean(equal)) = (
self.less_than_without_unit(&right)?,
self.eq_without_unit(&right)?,
) {
Ok(KalkValue::Boolean(less || equal))
} else {
unreachable!()
}
}
pub(crate) fn and(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self, rhs) {
(KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
Ok(KalkValue::Boolean(boolean && *boolean_rhs))
}
(lhs, rhs) => Err(KalkError::IncompatibleTypesForOperation(
String::from("and"),
lhs.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn or(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self, rhs) {
(KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
Ok(KalkValue::Boolean(boolean || *boolean_rhs))
}
(lhs, rhs) => Err(KalkError::IncompatibleTypesForOperation(
String::from("or"),
lhs.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn add_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self.clone(), rhs) {
(
KalkValue::Number(real, imaginary, _),
KalkValue::Number(real_rhs, imaginary_rhs, unit),
) => Ok(KalkValue::Number(
real + real_rhs,
imaginary + imaginary_rhs,
unit.clone(),
)),
(KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => {
calculate_matrix(self, rhs, &KalkValue::add_without_unit)
}
(KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
calculate_vector(self, rhs, &KalkValue::add_without_unit)
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("addition"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn sub_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self.clone(), rhs) {
(
KalkValue::Number(real, imaginary, _),
KalkValue::Number(real_rhs, imaginary_rhs, unit),
) => Ok(KalkValue::Number(
real - real_rhs,
imaginary - imaginary_rhs,
unit.clone(),
)),
(KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => {
calculate_matrix(self, rhs, &KalkValue::sub_without_unit)
}
(KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
calculate_vector(self, rhs, &KalkValue::sub_without_unit)
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("subtraction"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn mul_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
// Make sure matrix is always first to avoid having to match
// different orders in the next match expression.
let (lhs, rhs) = match (&self, rhs) {
(KalkValue::Matrix(_), KalkValue::Matrix(_)) => (&self, rhs),
(_, KalkValue::Matrix(_)) => (rhs, &self),
_ => (&self, rhs),
};
match (lhs, rhs) {
(
KalkValue::Number(real, imaginary, _),
KalkValue::Number(real_rhs, imaginary_rhs, unit),
) => Ok(KalkValue::Number(
// (a + bi)(c + di) = ac + adi + bci + bdi²
real.clone() * real_rhs - imaginary.clone() * imaginary_rhs,
real.clone() * imaginary_rhs + imaginary * real_rhs,
unit.clone(),
)),
(KalkValue::Matrix(_), KalkValue::Number(_, _, _)) => {
calculate_matrix(lhs.clone(), rhs, &KalkValue::mul_without_unit)
}
(KalkValue::Matrix(rows), KalkValue::Vector(values_rhs)) => {
if rows.first().unwrap().len() != values_rhs.len() {
return Err(KalkError::IncompatibleVectorsMatrixes);
}
let mut new_values: Vec<KalkValue> = Vec::new();
for row in rows {
let mut sum = KalkValue::from(0);
for (x, y) in row.iter().zip(values_rhs) {
sum = sum
.clone()
.add_without_unit(&x.clone().mul_without_unit(y)?)?;
}
new_values.push(sum);
}
Ok(KalkValue::Vector(new_values))
}
(KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => {
let lhs_columns = rows.first().unwrap();
if lhs_columns.len() != rows_rhs.len() {
return Err(KalkError::IncompatibleVectorsMatrixes);
}
let rhs_columns = rows_rhs.first().unwrap();
let mut result = vec![vec![KalkValue::from(0f64); rhs_columns.len()]; rows.len()];
// For every row in lhs
for i in 0..rows.len() {
// For every column in rhs
for j in 0..rhs_columns.len() {
let mut sum = KalkValue::from(0f64);
// For every value in the current lhs row
for (k, value) in rows[i].iter().enumerate() {
let value_rhs = &rows_rhs[k][j];
sum =
sum.add_without_unit(&value.clone().mul_without_unit(value_rhs)?)?;
}
result[i][j] = sum;
}
}
Ok(KalkValue::Matrix(result))
}
(KalkValue::Vector(values), KalkValue::Number(_, _, _)) => {
let mut multiplied_values = Vec::new();
for value in values {
multiplied_values.push(value.clone().mul_without_unit(rhs)?);
}
Ok(KalkValue::Vector(multiplied_values))
}
(KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => {
if values.len() != values_rhs.len() {
return Err(KalkError::IncompatibleVectorsMatrixes);
}
let mut sum = KalkValue::from(0f64);
for (value, value_rhs) in values.iter().zip(values_rhs) {
sum = sum.add_without_unit(&value.clone().mul_without_unit(value_rhs)?)?;
}
Ok(sum)
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("multiplication"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn div_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self.clone(), rhs.clone()) {
(KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, unit)) => {
// Avoid unecessary calculations
if !self.has_imaginary() && !rhs.has_imaginary() {
Ok(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.mul_without_unit(&conjugate)?.values();
let (denominator, _) = rhs.clone().mul_without_unit(&conjugate)?.values();
Ok(KalkValue::Number(
numerator / denominator.clone(),
numerator_imaginary / denominator,
unit,
))
}
}
(KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => {
calculate_matrix(self, rhs, &KalkValue::div_without_unit)
}
(KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
calculate_vector(self, rhs, &KalkValue::div_without_unit)
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("division"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn pow_without_unit(self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self.clone(), rhs) {
(
KalkValue::Number(real, imaginary, _),
KalkValue::Number(real_rhs, imaginary_rhs, unit),
) => {
if self.has_imaginary()
|| imaginary_rhs != &0f64
|| (real < 0f64 && real_rhs < &1f64)
{
let a = real;
let b = imaginary;
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.clone() / 2f64 * raised.ln();
Ok(KalkValue::Number(
polar.clone().cos() * exp.clone(),
polar.sin() * exp,
unit.clone(),
))
} else {
Ok(KalkValue::Number(
pow(real, real_rhs.clone()),
float!(0),
unit.clone(),
))
}
}
(KalkValue::Matrix(rows), KalkValue::Number(real, _, _)) => {
if real < &0f64 || real.clone().fract() > 0.000001f64 {
return Err(KalkError::Expected(String::from("a positive integer")));
}
if rhs.has_imaginary() {
return Err(KalkError::ExpectedReal);
}
if rows.len() != rows.first().unwrap().len() {
return Err(KalkError::Expected(String::from("a square matrix")));
}
if real == &0f64 {
return Ok(KalkValue::from(1f64));
}
let mut result = KalkValue::from(1f64);
for _ in 0..primitive!(real) as i32 {
result = result.mul_without_unit(&self)?;
}
Ok(result)
}
(KalkValue::Number(_, _, _), KalkValue::Matrix(rows)) => {
let mut new_rows = Vec::new();
for row in rows {
new_rows.push(Vec::new());
for item in row {
new_rows
.last_mut()
.unwrap()
.push(self.clone().pow_without_unit(item)?);
}
}
Ok(KalkValue::Matrix(new_rows))
}
(KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => {
calculate_vector(self, rhs, &KalkValue::pow_without_unit)
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("pow"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn eq_without_unit(&self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self, rhs) {
(
KalkValue::Number(real, imaginary, _),
KalkValue::Number(real_rhs, imaginary_rhs, _),
) => Ok(KalkValue::Boolean(
(real.clone() - real_rhs.clone()).abs() < ACCEPTABLE_COMPARISON_MARGIN
&& (imaginary.clone() - imaginary_rhs.clone()).abs()
< ACCEPTABLE_COMPARISON_MARGIN,
)),
(KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
Ok(KalkValue::Boolean(boolean == boolean_rhs))
}
(KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => {
let mut matrices_are_equal = true;
for (row, row_rhs) in rows.iter().zip(rows_rhs) {
for (value, value_rhs) in row.iter().zip(row_rhs) {
if let KalkValue::Boolean(are_equal) = value.eq_without_unit(value_rhs)? {
if !are_equal {
matrices_are_equal = false;
}
}
}
}
Ok(KalkValue::Boolean(matrices_are_equal))
}
(KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => {
let mut vecs_are_equal = true;
for (value, value_rhs) in values.iter().zip(values_rhs) {
if let KalkValue::Boolean(are_equal) = value.eq_without_unit(value_rhs)? {
if !are_equal {
vecs_are_equal = false;
}
}
}
Ok(KalkValue::Boolean(vecs_are_equal))
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("equal"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn not_eq_without_unit(&self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
match (self, rhs) {
(
KalkValue::Number(real, imaginary, _),
KalkValue::Number(real_rhs, imaginary_rhs, _),
) => Ok(KalkValue::Boolean(
(real.clone() - real_rhs.clone()).abs() > ACCEPTABLE_COMPARISON_MARGIN
|| (imaginary.clone() - imaginary_rhs.clone()).abs()
> ACCEPTABLE_COMPARISON_MARGIN,
)),
(KalkValue::Boolean(boolean), KalkValue::Boolean(boolean_rhs)) => {
Ok(KalkValue::Boolean(boolean != boolean_rhs))
}
(KalkValue::Vector(_), KalkValue::Vector(_))
| (KalkValue::Matrix(_), KalkValue::Matrix(_)) => {
if let KalkValue::Boolean(boolean) = self.eq_without_unit(rhs)? {
Ok(KalkValue::Boolean(!boolean))
} else {
unreachable!()
}
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("not equal"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn greater_than_without_unit(
&self,
rhs: &KalkValue,
) -> Result<KalkValue, KalkError> {
if self.has_imaginary() || rhs.has_imaginary() {
return Err(KalkError::ExpectedReal);
}
match (self, rhs) {
(KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => Ok(
KalkValue::Boolean(real.clone() - real_rhs.clone() > ACCEPTABLE_COMPARISON_MARGIN),
),
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("greater than"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub(crate) fn less_than_without_unit(&self, rhs: &KalkValue) -> Result<KalkValue, KalkError> {
if self.has_imaginary() || rhs.has_imaginary() {
return Err(KalkError::ExpectedReal);
}
match (self, rhs) {
(KalkValue::Number(real, _, _), KalkValue::Number(real_rhs, _, _)) => Ok(
KalkValue::Boolean(real.clone() - real_rhs.clone() < -ACCEPTABLE_COMPARISON_MARGIN),
),
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("less than"),
self.get_type_name(),
rhs.get_type_name(),
)),
}
}
pub fn get_conjugate(&self) -> Result<KalkValue, KalkError> {
match self {
KalkValue::Number(real, imaginary, unit) => Ok(KalkValue::Number(
real.clone(),
imaginary.clone() * (-1f64),
unit.clone(),
)),
_ => Err(KalkError::UnexpectedType(
self.get_type_name(),
vec![String::from("number")],
)),
}
}
}
pub fn format_number(input: f64) -> String {
let rounded = format!("{:.1$}", input, 10);
let result = if rounded.contains('.') {
rounded
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
} else {
rounded
};
spaced(&result)
}
fn calculate_vector(
x: KalkValue,
y: &KalkValue,
action: &dyn Fn(KalkValue, &KalkValue) -> Result<KalkValue, KalkError>,
) -> Result<KalkValue, KalkError> {
match (x, y) {
(KalkValue::Vector(values), KalkValue::Number(_, _, _)) => {
let mut new_values = Vec::new();
for value in values {
new_values.push(action(value.clone(), y)?);
}
Ok(KalkValue::Vector(new_values))
}
(KalkValue::Number(_, _, _), KalkValue::Vector(values_rhs)) => {
let mut new_values = Vec::new();
for value in values_rhs {
new_values.push(action(y.clone(), value)?);
}
Ok(KalkValue::Vector(new_values))
}
(KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => {
if values.len() != values_rhs.len() {
return Err(KalkError::IncompatibleVectorsMatrixes);
}
let mut new_values = Vec::new();
for (value, value_rhs) in values.iter().zip(values_rhs) {
new_values.push(action(value.clone(), value_rhs)?);
}
Ok(KalkValue::Vector(new_values))
}
(x, y) => Err(KalkError::IncompatibleTypesForOperation(
String::from("vector operation"),
x.get_type_name(),
y.get_type_name(),
)),
}
}
fn calculate_matrix(
x: KalkValue,
y: &KalkValue,
action: &dyn Fn(KalkValue, &KalkValue) -> Result<KalkValue, KalkError>,
) -> Result<KalkValue, KalkError> {
// Make sure matrix is always first to avoid having to match
// different orders in the next match expression.
let (x, y) = match (&x, y) {
(KalkValue::Matrix(_), KalkValue::Matrix(_)) => (&x, y),
(_, KalkValue::Matrix(_)) => (y, &x),
_ => (&x, y),
};
match (x, y) {
(KalkValue::Matrix(rows), KalkValue::Number(_, _, _)) => {
let mut new_rows = Vec::new();
for row in rows {
new_rows.push(Vec::new());
for item in row {
new_rows.last_mut().unwrap().push(action(item.clone(), y)?);
}
}
Ok(KalkValue::Matrix(new_rows))
}
(KalkValue::Matrix(rows), KalkValue::Vector(values_rhs)) => {
if rows.len() != values_rhs.len() {
return Err(KalkError::IncompatibleVectorsMatrixes);
}
let mut new_rows = Vec::new();
for (i, row) in rows.iter().enumerate() {
new_rows.push(Vec::new());
for value in row {
new_rows
.last_mut()
.unwrap()
.push(action(value.clone(), &values_rhs[i])?)
}
}
Ok(KalkValue::Matrix(new_rows))
}
(KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => {
if rows.len() != rows_rhs.len()
|| rows.first().unwrap().len() != rows_rhs.first().unwrap().len()
{
return Err(KalkError::IncompatibleVectorsMatrixes);
}
let mut new_rows = Vec::new();
for (i, row) in rows.iter().enumerate() {
new_rows.push(Vec::new());
for (j, value) in row.iter().enumerate() {
new_rows
.last_mut()
.unwrap()
.push(action(value.clone(), &rows_rhs[i][j])?)
}
}
Ok(KalkValue::Matrix(new_rows))
}
_ => Err(KalkError::IncompatibleTypesForOperation(
String::from("matrix operation"),
x.get_type_name(),
y.get_type_name(),
)),
}
}
fn spaced(number_str: &str) -> String {
let dot_pos = number_str.find('.');
let integer_boundary = if let Some(dot_pos) = dot_pos {
dot_pos
} else {
number_str.len()
};
if integer_boundary < 5 {
return number_str.into();
}
let bytes = number_str.as_bytes();
let mut at_decimals = dot_pos.is_some();
let mut i = number_str.len() - 1;
let mut c = 0;
let mut new_str = String::new();
while i > 0 {
if bytes[i] as char == '.' {
new_str.push('.');
at_decimals = false;
i -= 1;
c = 0;
continue;
}
if !at_decimals && c == 3 {
new_str.push(' ');
c = 0;
}
new_str.push(bytes[i] as char);
c += 1;
i -= 1;
}
if c == 3 {
new_str.push(' ');
}
new_str.push(bytes[0] as char);
new_str.chars().rev().collect::<String>()
}
fn calculate_unit(
context: &mut crate::interpreter::Context,
left: &KalkValue,
right: KalkValue,
) -> Option<KalkValue> {
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.as_ref().unwrap())
} else {
Some(KalkValue::Number(
real_right.clone(),
imaginary_right.clone(),
unit_left.clone(),
))
}
} else {
None
}
}
#[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 From<ScientificNotation> for String {
fn from(val: ScientificNotation) -> Self {
val.to_string()
}
}
/*impl std::iter::Sum<KalkValue> for KalkValue {
fn sum<I>(iter: I) -> KalkValue
where
I: std::iter::Iterator<Item = KalkValue>,
{
let mut sum = KalkValue::from(0f64);
for x in iter {
sum = sum.add_without_unit(&x);
}
sum
}
}*/
impl From<KalkValue> for String {
fn from(val: KalkValue) -> Self {
val.to_string()
}
}
impl From<KalkValue> for f64 {
fn from(val: KalkValue) -> Self {
val.to_f64()
}
}
impl From<f64> for KalkValue {
fn from(x: f64) -> Self {
KalkValue::Number(float!(x), float!(0), None)
}
}
impl From<f32> for KalkValue {
fn from(x: f32) -> Self {
KalkValue::Number(float!(x), float!(0), None)
}
}
impl From<i128> for KalkValue {
fn from(x: i128) -> Self {
KalkValue::Number(float!(x), float!(0), None)
}
}
impl From<i64> for KalkValue {
fn from(x: i64) -> Self {
KalkValue::Number(float!(x), float!(0), None)
}
}
impl From<i32> for KalkValue {
fn from(x: i32) -> Self {
KalkValue::Number(float!(x), float!(0), None)
}
}
#[cfg(test)]
mod tests {
use crate::kalk_value::{spaced, KalkValue};
use crate::test_helpers::cmp;
#[test]
fn test_spaced() {
assert_eq!(spaced("1"), String::from("1"));
assert_eq!(spaced("10"), String::from("10"));
assert_eq!(spaced("100"), String::from("100"));
assert_eq!(spaced("1000"), String::from("1000"));
assert_eq!(spaced("10000"), String::from("10 000"));
assert_eq!(spaced("100000"), String::from("100 000"));
assert_eq!(spaced("1000000"), String::from("1 000 000"));
assert_eq!(spaced("10000000"), String::from("10 000 000"));
assert_eq!(spaced("1.12345"), String::from("1.12345"));
assert_eq!(spaced("10.12345"), String::from("10.12345"));
assert_eq!(spaced("100.12345"), String::from("100.12345"));
assert_eq!(spaced("1000.12345"), String::from("1000.12345"));
assert_eq!(spaced("10000.12345"), String::from("10 000.12345"));
assert_eq!(spaced("100000.12345"), String::from("100 000.12345"));
assert_eq!(spaced("1000000.12345"), String::from("1 000 000.12345"));
assert_eq!(spaced("10000000.12345"), String::from("10 000 000.12345"));
}
#[test]
fn test_add_complex() {
let in_out = vec![
((0f64, 0f64), (0f64, 0f64), (0f64, 0f64)),
((2f64, 0f64), (3f64, 4f64), (5f64, 4f64)),
((0f64, 2f64), (3f64, 4f64), (3f64, 6f64)),
((3f64, -2f64), (-3f64, 4f64), (0f64, 2f64)),
];
for (a, b, expected_result) in in_out {
let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
.add_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
.unwrap();
assert_eq!(actual_result.to_f64(), expected_result.0);
assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
}
}
#[test]
fn test_sub_complex() {
let in_out = vec![
((0f64, 0f64), (0f64, 0f64), (0f64, 0f64)),
((2f64, 0f64), (3f64, 4f64), (-1f64, -4f64)),
((0f64, 2f64), (3f64, 4f64), (-3f64, -2f64)),
((3f64, -2f64), (-3f64, 4f64), (6f64, -6f64)),
];
for (a, b, expected_result) in in_out {
let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
.sub_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
.unwrap();
assert_eq!(actual_result.to_f64(), expected_result.0);
assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
}
}
#[test]
fn test_mul_complex() {
let in_out = vec![
((0f64, 0f64), (0f64, 0f64), (0f64, 0f64)),
((2f64, 0f64), (3f64, 4f64), (6f64, 8f64)),
((0f64, 2f64), (3f64, 4f64), (-8f64, 6f64)),
((3f64, -2f64), (-3f64, 4f64), (-1f64, 18f64)),
];
for (a, b, expected_result) in in_out {
let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
.mul_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
.unwrap();
assert_eq!(actual_result.to_f64(), expected_result.0);
assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
}
}
#[test]
fn test_div_complex() {
let in_out = vec![
((2f64, 0f64), (3f64, 4f64), (0.24f64, -0.32f64)),
((0f64, 2f64), (3f64, 4f64), (0.32f64, 0.24f64)),
((3f64, -2f64), (-3f64, 4f64), (-0.68f64, -0.24f64)),
];
for (a, b, expected_result) in in_out {
let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
.div_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
.unwrap();
assert_eq!(actual_result.to_f64(), expected_result.0);
assert_eq!(actual_result.imaginary_to_f64(), expected_result.1);
}
}
#[test]
fn test_pow_complex() {
let in_out = vec![
((2f64, 0f64), (0f64, 3f64), (-0.4869944f64, 0.8734050f64)),
((2f64, 0f64), (2f64, 3f64), (-1.9479776f64, 3.4936203f64)),
((0f64, 2f64), (0f64, 3f64), (-0.0043748f64, 0.0078460f64)),
((3f64, 2f64), (0f64, 3f64), (-0.1304148f64, -0.111153f64)),
((3f64, 2f64), (4f64, 3f64), (28.8577819f64, -2.422530f64)),
(
(3f64, 0f64),
(0f64, 1f64 / 3f64),
(0.9336932f64, 0.3580738f64),
),
(
(3f64, 4f64),
(0f64, 1f64 / 4f64),
(0.7297490f64, 0.3105648f64),
),
];
for (a, b, expected_result) in in_out {
let actual_result = KalkValue::Number(float!(a.0), float!(a.1), None)
.pow_without_unit(&KalkValue::Number(float!(b.0), float!(b.1), None))
.unwrap();
assert!(cmp(actual_result.to_f64(), expected_result.0));
assert!(cmp(actual_result.imaginary_to_f64(), expected_result.1));
}
}
#[test]
fn test_to_string_pretty() {
let in_out = vec![
(0.99999, 0.0, "0.99999 ≈ 1"),
(-0.99999, 0.0, "-0.99999 ≈ -1"),
(0.0, 0.99999, "0.99999i ≈ i"),
(0.000000001, 0.0, "10^-9 ≈ 0"),
(0.0, 0.000000001, "10^-9 i ≈ 0"),
(0.99999, 0.999999, "0.99999 + 0.999999i ≈ 1 + i"),
(1.0, 0.99999, "1 + 0.99999i ≈ 1 + i"),
(-0.99999, 0.999999, "-0.99999 + 0.999999i ≈ -1 + i"),
(0.99999, -0.999999, "0.99999 - 0.999999i ≈ 1 - i"),
(-1.0, 0.99999, "-1 + 0.99999i ≈ -1 + i"),
(1.0, -0.99999, "1 - 0.99999i ≈ 1 - i"),
(-0.99999, 1.0, "-0.99999 + i ≈ -1 + i"),
(0.99999, -1.0, "0.99999 - i ≈ 1 - i"),
(0.000000001, 0.000000001, "10^-9 + 10^-9 i ≈ 0"),
(1.0, 0.000000001, "1 + 10^-9 i ≈ 1"),
(0.000000001, 1.0, "10^-9 + i ≈ i"),
(-1.0, 0.000000001, "-1 + 10^-9 i ≈ -1"),
(0.000000001, -1.0, "10^-9 - i ≈ -i"),
(10e-17, 1.0, "i"),
(1.0, 10e-17, "1"),
(10e-16, 0.0, "0"),
(3.00000000004, 0.0, "3"),
];
for (real, imaginary, output) in in_out {
let result =
KalkValue::Number(float!(real), float!(imaginary), None).to_string_pretty();
assert_eq!(output, result);
}
}
}