Split all datatypes
This commit is contained in:
parent
b5e944e82c
commit
2f2976bfd7
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -231,7 +231,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rpn_rs"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"confy",
|
||||
"crossterm",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rpn_rs"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
description = "A TUI RPN calculator, similar to Orpie"
|
||||
authors = ["Austen Adler <agadler@austenadler.com>"]
|
||||
edition = "2018"
|
||||
|
22
pipeline.yml
Normal file
22
pipeline.yml
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
resources:
|
||||
- name: source
|
||||
type: git
|
||||
source:
|
||||
uri: https://gitea.austen-wares.com/stonewareslord/rpn_rs
|
||||
branch: develop
|
||||
jobs:
|
||||
- name: build
|
||||
serial: true
|
||||
plan:
|
||||
- get: source
|
||||
- task: build
|
||||
config:
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: docker-image
|
||||
source: {repository: }
|
||||
inputs:
|
||||
- name: source
|
||||
outputs:
|
||||
- name: out
|
@ -850,14 +850,14 @@ impl Calculator {
|
||||
/// Checks if a value on the stack is equal to a given value
|
||||
fn stack_eq(&mut self, idx: usize, value: &Entry) -> CalculatorResult<()> {
|
||||
if self.peek(idx)? == *value {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CalculatorError::CorruptStateChange(format!(
|
||||
"Stack index {} should be {}, but is {}",
|
||||
idx,
|
||||
value,
|
||||
self.peek(idx)?,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
1211
src/calc/entries.rs
1211
src/calc/entries.rs
File diff suppressed because it is too large
Load Diff
353
src/calc/entries/matrix.rs
Normal file
353
src/calc/entries/matrix.rs
Normal file
@ -0,0 +1,353 @@
|
||||
use super::VectorDirection;
|
||||
use super::{Entry, Number, Vector};
|
||||
use crate::calc::errors::{CalculatorError, CalculatorResult};
|
||||
use crate::calc::types::CalculatorAngleMode;
|
||||
use crate::calc::CalculatorDisplayMode;
|
||||
use crate::CalculatorEntry;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct MatrixDimensions {
|
||||
pub rows: usize,
|
||||
pub columns: usize,
|
||||
}
|
||||
|
||||
impl MatrixDimensions {
|
||||
pub const fn transpose(&self) -> Self {
|
||||
Self {
|
||||
rows: self.columns,
|
||||
columns: self.rows,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Matrix {
|
||||
pub vectors: Vec<Vector>,
|
||||
pub dimensions: MatrixDimensions,
|
||||
}
|
||||
impl Matrix {
|
||||
pub fn from(entries: &[Entry]) -> CalculatorResult<Entry> {
|
||||
if entries.is_empty() {
|
||||
return Err(CalculatorError::NotEnoughStackEntries);
|
||||
}
|
||||
|
||||
let vectors = entries
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
Entry::Matrix(_) | Entry::Number(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(vector) => Ok(vector.clone()),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?;
|
||||
|
||||
// Get the num_rows and dimension of the matrix
|
||||
let first_vector = vectors
|
||||
.get(0)
|
||||
.ok_or(CalculatorError::NotEnoughStackEntries)?;
|
||||
|
||||
// The number of rows in this column-based matrix
|
||||
let num_rows = first_vector.values.len();
|
||||
// The direction all vectors must face
|
||||
let vector_direction = first_vector.direction;
|
||||
|
||||
// Either the dimension lengths mismatch, or the vectors are facing different directions (and are longer than 1, since a 1-length vector orientation does not matter
|
||||
if vectors.iter().any(|v| v.values.len() != num_rows)
|
||||
|| (num_rows > 1 && vectors.iter().any(|v| v.direction != vector_direction))
|
||||
{
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
|
||||
let dimensions = MatrixDimensions {
|
||||
rows: num_rows,
|
||||
columns: vectors.len(),
|
||||
};
|
||||
|
||||
let ret = Self {
|
||||
vectors,
|
||||
dimensions,
|
||||
};
|
||||
|
||||
// If the user tried making a matrix out of row vectors, we need to transpose it, which forces column vectors
|
||||
if vector_direction == VectorDirection::Row && num_rows > 1 {
|
||||
ret.transpose()
|
||||
} else {
|
||||
ret.validate()
|
||||
}
|
||||
}
|
||||
|
||||
fn iterated_unary(
|
||||
&self,
|
||||
op: impl Fn(&Vector) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Self {
|
||||
vectors: self
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| op(v))
|
||||
.map(|e| match e {
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
dimensions: self.dimensions.clone(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
|
||||
fn iterated_binary_num(
|
||||
&self,
|
||||
number: &Number,
|
||||
op: impl Fn(&Vector, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Self {
|
||||
vectors: self
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| op(v, &Entry::Number(*number)))
|
||||
.map(|e| match e {
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
dimensions: self.dimensions.clone(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
|
||||
fn iterated_binary_mat(
|
||||
&self,
|
||||
m2: &Self,
|
||||
op: impl Fn(&Vector, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
if self.dimensions != m2.dimensions {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
Self {
|
||||
vectors: self
|
||||
.vectors
|
||||
.iter()
|
||||
.zip(m2.vectors.iter())
|
||||
.map(|(v1, v2)| op(v1, &Entry::Vector(v2.clone())))
|
||||
.map(|e| match e {
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
dimensions: self.dimensions.clone(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalculatorEntry for Matrix {
|
||||
fn to_editable_string(&self) -> CalculatorResult<String> {
|
||||
// TODO: Eventualy we can parse and edit a matrix as a string
|
||||
Err(CalculatorError::TypeMismatch)
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
// The the number of vectors is equal to the 0th dimension
|
||||
self.vectors.len() == self.dimensions.columns
|
||||
// The number of elements in all vectors are equal to the 1st dimension, and each is valid
|
||||
&& self
|
||||
.vectors
|
||||
.iter()
|
||||
.all(|v| v.values.len() == self.dimensions.rows && v.is_valid())
|
||||
// The dimensions are not zero
|
||||
&& self.dimensions.rows > 0 && self.dimensions.columns > 0
|
||||
}
|
||||
fn validate(self) -> CalculatorResult<Entry> {
|
||||
if self.is_valid() {
|
||||
Ok(Entry::Matrix(self))
|
||||
} else {
|
||||
Err(CalculatorError::ArithmeticError)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_entry(&self, display_mode: &CalculatorDisplayMode) -> String {
|
||||
format!(
|
||||
"[ {} ]",
|
||||
self.vectors
|
||||
.iter()
|
||||
.map(|vector| vector.format_entry(display_mode))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
)
|
||||
}
|
||||
|
||||
// Mathematical operations
|
||||
fn negate(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::negate)
|
||||
}
|
||||
fn abs(&self) -> CalculatorResult<Entry> {
|
||||
// TODO: Compute determinant
|
||||
Err(CalculatorError::NotYetImplemented)
|
||||
}
|
||||
fn inverse(&self) -> CalculatorResult<Entry> {
|
||||
// TODO: Inverse
|
||||
Err(CalculatorError::NotYetImplemented)
|
||||
}
|
||||
fn transpose(&self) -> CalculatorResult<Entry> {
|
||||
// Iterate over all rows
|
||||
let mut vectors: Vec<Vector> = vec![];
|
||||
for r in 0..self.dimensions.rows {
|
||||
vectors.push(Vector {
|
||||
values: self
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| {
|
||||
// For each row, get the r'th element to build a new vector
|
||||
v.values
|
||||
.get(r)
|
||||
.map_or_else(|| Err(CalculatorError::DimensionMismatch), |n| Ok(*n))
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Number>>>()?,
|
||||
direction: VectorDirection::Column,
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
vectors,
|
||||
dimensions: self.dimensions.transpose(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.sin(angle_mode))
|
||||
}
|
||||
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.cos(angle_mode))
|
||||
}
|
||||
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.tan(angle_mode))
|
||||
}
|
||||
fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.asin(angle_mode))
|
||||
}
|
||||
fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.acos(angle_mode))
|
||||
}
|
||||
fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.atan(angle_mode))
|
||||
}
|
||||
fn sqrt(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::sqrt)
|
||||
}
|
||||
fn log(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::log)
|
||||
}
|
||||
fn ln(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::ln)
|
||||
}
|
||||
|
||||
// Binary
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::add),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::add),
|
||||
}
|
||||
}
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::sub),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::sub),
|
||||
}
|
||||
}
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => {
|
||||
if self.dimensions.columns != m2.dimensions.rows {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
let dimensions = MatrixDimensions {
|
||||
rows: self.dimensions.rows,
|
||||
columns: m2.dimensions.columns,
|
||||
};
|
||||
|
||||
// A matrix is a list of column vectors, so transpose self and zip the columns
|
||||
let transposed_self: Self = match self.transpose()? {
|
||||
Entry::Matrix(t) => t,
|
||||
_ => {
|
||||
return Err(CalculatorError::InternalError(String::from(
|
||||
"Matrix transpose produced wrong type",
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let mut vectors: Vec<Vector> = vec![];
|
||||
|
||||
for c in &m2.vectors {
|
||||
let mut vector: Vector = Vector {
|
||||
values: vec![],
|
||||
direction: VectorDirection::Column,
|
||||
};
|
||||
for r in &transposed_self.vectors {
|
||||
if let Entry::Number(number) =
|
||||
c.transpose()?.mul(&Entry::Vector(r.clone()))?
|
||||
{
|
||||
vector.values.push(number);
|
||||
} else {
|
||||
return Err(CalculatorError::InternalError(String::from(
|
||||
"Vector multiplication did not produce a number",
|
||||
)));
|
||||
}
|
||||
}
|
||||
vectors.push(vector);
|
||||
}
|
||||
|
||||
Self {
|
||||
vectors,
|
||||
dimensions,
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
Entry::Vector(vector) => self.mul(&Self::from(
|
||||
&[Entry::Vector(vector.clone())], // Treat a vector as a 1D matrix
|
||||
)?),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::mul),
|
||||
}
|
||||
}
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::div),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::div),
|
||||
}
|
||||
}
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::int_divide),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::int_divide),
|
||||
}
|
||||
}
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::modulo),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::modulo),
|
||||
}
|
||||
}
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_m2) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::pow),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Matrix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[ {} ]",
|
||||
self.vectors
|
||||
.iter()
|
||||
.map(|vector| format!("{}", vector))
|
||||
.collect::<Vec<String>>()
|
||||
.join("; ")
|
||||
)
|
||||
}
|
||||
}
|
339
src/calc/entries/number.rs
Normal file
339
src/calc/entries/number.rs
Normal file
@ -0,0 +1,339 @@
|
||||
use super::VectorDirection;
|
||||
use super::{Entry, Matrix, Vector};
|
||||
use crate::calc::errors::{CalculatorError, CalculatorResult};
|
||||
use crate::calc::types::CalculatorAngleMode;
|
||||
use crate::calc::CalculatorDisplayMode;
|
||||
use crate::CalculatorEntry;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Number {
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
impl PartialEq for Number {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.value.is_nan() && other.value.is_nan()
|
||||
|| self.value.is_infinite() && other.value.is_infinite()
|
||||
{
|
||||
true
|
||||
} else if self.value.is_nan()
|
||||
|| self.value.is_infinite()
|
||||
|| other.value.is_infinite()
|
||||
|| other.value.is_nan()
|
||||
{
|
||||
false
|
||||
} else {
|
||||
(self.value - other.value).abs() < f64::EPSILON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalculatorEntry for Number {
|
||||
fn to_editable_string(&self) -> CalculatorResult<String> {
|
||||
Ok(format!("{}", self.value))
|
||||
}
|
||||
fn format_entry(&self, display_mode: &CalculatorDisplayMode) -> String {
|
||||
match display_mode {
|
||||
CalculatorDisplayMode::Default => format!("{}", self.value),
|
||||
CalculatorDisplayMode::Separated { separator } => separated(self.value, *separator),
|
||||
CalculatorDisplayMode::Scientific { precision } => scientific(self.value, *precision),
|
||||
CalculatorDisplayMode::Engineering { precision } => engineering(self.value, *precision),
|
||||
CalculatorDisplayMode::Fixed { precision } => {
|
||||
format!("{:0>.precision$}", self.value, precision = precision)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
!self.value.is_nan() && !self.value.is_infinite()
|
||||
}
|
||||
fn validate(self) -> CalculatorResult<Entry> {
|
||||
if self.is_valid() {
|
||||
Ok(Entry::Number(self))
|
||||
} else {
|
||||
Err(CalculatorError::ArithmeticError)
|
||||
}
|
||||
}
|
||||
|
||||
fn negate(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self { value: -self.value }))
|
||||
}
|
||||
fn abs(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self.value.abs(),
|
||||
}))
|
||||
}
|
||||
fn inverse(&self) -> CalculatorResult<Entry> {
|
||||
Self {
|
||||
value: self.value.recip(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
fn transpose(&self) -> CalculatorResult<Entry> {
|
||||
Err(CalculatorError::TypeMismatch)
|
||||
}
|
||||
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.to_radians().sin(),
|
||||
CalculatorAngleMode::Radians => self.value.sin(),
|
||||
CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).sin(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.to_radians().cos(),
|
||||
CalculatorAngleMode::Radians => self.value.cos(),
|
||||
CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).cos(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.to_radians().tan(),
|
||||
CalculatorAngleMode::Radians => self.value.tan(),
|
||||
CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).tan(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.asin().to_degrees(),
|
||||
CalculatorAngleMode::Radians => self.value.asin(),
|
||||
CalculatorAngleMode::Grads => self.value.asin() * 200.0 / std::f64::consts::PI,
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.acos().to_degrees(),
|
||||
CalculatorAngleMode::Radians => self.value.acos(),
|
||||
CalculatorAngleMode::Grads => self.value.acos() * 200.0 / std::f64::consts::PI,
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.atan().to_degrees(),
|
||||
CalculatorAngleMode::Radians => self.value.atan(),
|
||||
CalculatorAngleMode::Grads => self.value.atan() * 200.0 / std::f64::consts::PI,
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn sqrt(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self.value.sqrt(),
|
||||
}))
|
||||
}
|
||||
fn log(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self.value.log10(),
|
||||
}))
|
||||
}
|
||||
fn ln(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self.value.ln(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::add),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::add),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value + number.value,
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::sub),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::sub),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value - number.value,
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::mul),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::mul),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value * number.value,
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::div),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::div),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value / number.value,
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::int_divide),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::int_divide),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value.div_euclid(number.value),
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::modulo),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::modulo),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value % number.value,
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::pow),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::pow),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value.powf(number.value),
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Number {
|
||||
pub const ZERO: Self = Self { value: 0.0_f64 };
|
||||
|
||||
fn iterated_binary_vec(
|
||||
self,
|
||||
vector: &Vector,
|
||||
op: impl Fn(&Self, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Vector(Vector {
|
||||
values: vector
|
||||
.values
|
||||
.iter()
|
||||
.map(|n| op(&self, &Entry::Number(*n)))
|
||||
.map(|e| match e {
|
||||
// Only numbers are valid in a vector
|
||||
Ok(Entry::Number(number)) => Ok(number),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Self>>>()?,
|
||||
direction: vector.direction,
|
||||
}))
|
||||
}
|
||||
|
||||
fn iterated_binary_mat(
|
||||
self,
|
||||
matrix: &Matrix,
|
||||
op: impl Fn(&Self, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Matrix {
|
||||
vectors: matrix
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| op(&self, &Entry::Vector(v.clone())))
|
||||
.map(|e| match e {
|
||||
// Only numbers are valid in a vector
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
dimensions: matrix.dimensions.clone(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://stackoverflow.com/a/65266882
|
||||
fn scientific(f: f64, precision: usize) -> String {
|
||||
let mut ret = format!("{:.precision$E}", f, precision = precision);
|
||||
let exp = ret.split_off(ret.find('E').unwrap_or(0));
|
||||
let (exp_sign, exp) = exp
|
||||
.strip_prefix("E-")
|
||||
.map_or_else(|| ('+', &exp[1..]), |stripped| ('-', stripped));
|
||||
|
||||
let sign = if ret.starts_with('-') { "" } else { " " };
|
||||
format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2)
|
||||
}
|
||||
|
||||
fn engineering(f: f64, precision: usize) -> String {
|
||||
// Format the string so the first digit is always in the first column, and remove '.'. Requested precision + 2 to account for using 1, 2, or 3 digits for the whole portion of the string
|
||||
// 1,000 => 1000E3
|
||||
let all = format!(" {:.precision$E}", f, precision = precision)
|
||||
// Remove . since it can be moved
|
||||
.replacen(".", "", 1)
|
||||
// Add 00E before E here so the length is enough for slicing below
|
||||
.replacen("E", "00E", 1);
|
||||
// Extract mantissa and the string representation of the exponent. Unwrap should be safe as formatter will insert E
|
||||
// 1000E3 => (1000, E3)
|
||||
let (num_str, exp_str) = all.split_at(all.find('E').unwrap());
|
||||
// Extract the exponent as an isize. This should always be true because Entry max will be ~400
|
||||
// E3 => 3 as isize
|
||||
let exp = exp_str[1..].parse::<isize>().unwrap();
|
||||
// Sign of the exponent. If string representation starts with E-, then negative
|
||||
let display_exp_sign = if exp_str.strip_prefix("E-").is_some() {
|
||||
'-'
|
||||
} else {
|
||||
'+'
|
||||
};
|
||||
|
||||
// The exponent to display. Always a multiple of 3 in engineering mode. Always positive because sign is added with display_exp_sign above
|
||||
// 100 => 0, 1000 => 3, .1 => 3 (but will show as -3)
|
||||
let display_exp = (exp.div_euclid(3) * 3).abs();
|
||||
// Number of whole digits. Always 1, 2, or 3 depending on exponent divisibility
|
||||
let num_whole_digits = exp.rem_euclid(3) as usize + 1;
|
||||
|
||||
// If this is a negative number, strip off the added space, otherwise keep the space (and next digit)
|
||||
let num_str = if num_str.strip_prefix(" -").is_some() {
|
||||
&num_str[1..]
|
||||
} else {
|
||||
num_str
|
||||
};
|
||||
|
||||
// Whole portion of number. Slice is safe because the num_whole_digits is always 3 and the num_str will always have length >= 3 since precision in all=2 (+original whole digit)
|
||||
// Original number is 1,000 => whole will be 1, if original is 0.01, whole will be 10
|
||||
let whole = &num_str[0..=num_whole_digits];
|
||||
// Decimal portion of the number. Sliced from the number of whole digits to the *requested* precision. Precision generated in all will be requested precision + 2
|
||||
let decimal = &num_str[(num_whole_digits + 1)..=(precision + num_whole_digits)];
|
||||
// Right align whole portion, always have decimal point
|
||||
format!(
|
||||
"{: >4}.{} E{}{:0>pad$}",
|
||||
// display_sign,
|
||||
whole,
|
||||
decimal,
|
||||
display_exp_sign,
|
||||
display_exp,
|
||||
pad = 2
|
||||
)
|
||||
}
|
||||
|
||||
fn separated(f: f64, sep: char) -> String {
|
||||
let mut ret = f.to_string();
|
||||
let start = if ret.starts_with('-') { 1 } else { 0 };
|
||||
let end = ret.find('.').unwrap_or_else(|| ret.len());
|
||||
for i in 0..((end - start - 1).div_euclid(3)) {
|
||||
ret.insert(end - (i + 1) * 3, sep);
|
||||
}
|
||||
ret
|
||||
}
|
291
src/calc/entries/vector.rs
Normal file
291
src/calc/entries/vector.rs
Normal file
@ -0,0 +1,291 @@
|
||||
use super::{Entry, Matrix, Number};
|
||||
use crate::calc::errors::{CalculatorError, CalculatorResult};
|
||||
use crate::calc::types::CalculatorAngleMode;
|
||||
use crate::calc::CalculatorDisplayMode;
|
||||
use crate::CalculatorEntry;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum VectorDirection {
|
||||
Row,
|
||||
Column,
|
||||
}
|
||||
|
||||
impl VectorDirection {
|
||||
pub const fn swap(&self) -> Self {
|
||||
match self {
|
||||
Self::Row => Self::Column,
|
||||
Self::Column => Self::Row,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_separator(&self) -> &str {
|
||||
match self {
|
||||
Self::Row => " ",
|
||||
Self::Column => "; ",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VectorDirection {
|
||||
fn default() -> Self {
|
||||
// Column vectors are the default
|
||||
Self::Column
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Vector {
|
||||
pub values: Vec<Number>,
|
||||
pub direction: VectorDirection,
|
||||
}
|
||||
|
||||
impl CalculatorEntry for Vector {
|
||||
// Misc
|
||||
fn to_editable_string(&self) -> CalculatorResult<String> {
|
||||
// TODO: Eventualy we can parse and edit a vector as a string
|
||||
Err(CalculatorError::TypeMismatch)
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
self.values.iter().all(|number| number.is_valid())
|
||||
}
|
||||
fn validate(self) -> CalculatorResult<Entry> {
|
||||
if self.is_valid() {
|
||||
Ok(Entry::Vector(self))
|
||||
} else {
|
||||
Err(CalculatorError::ArithmeticError)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_entry(&self, display_mode: &CalculatorDisplayMode) -> String {
|
||||
format!(
|
||||
"[{}]",
|
||||
self.values
|
||||
.iter()
|
||||
.map(|number| number.format_entry(display_mode))
|
||||
.collect::<Vec<String>>()
|
||||
.join(self.direction.get_separator())
|
||||
)
|
||||
}
|
||||
// Mathematical operations
|
||||
fn negate(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Number::negate)
|
||||
}
|
||||
fn abs(&self) -> CalculatorResult<Entry> {
|
||||
let value: Entry = self
|
||||
.values
|
||||
.iter()
|
||||
.try_fold(Entry::Number(Number::ZERO), |acc, n2| {
|
||||
acc.add(&n2.pow(&Entry::Number(Number { value: 2.0_f64 }))?)
|
||||
})?;
|
||||
value.sqrt()
|
||||
}
|
||||
fn inverse(&self) -> CalculatorResult<Entry> {
|
||||
Err(CalculatorError::TypeMismatch)
|
||||
}
|
||||
fn transpose(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Vector(Self {
|
||||
values: self.values.clone(),
|
||||
direction: self.direction.swap(),
|
||||
}))
|
||||
}
|
||||
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|n| n.sin(angle_mode))
|
||||
}
|
||||
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|n| n.cos(angle_mode))
|
||||
}
|
||||
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|n| n.tan(angle_mode))
|
||||
}
|
||||
fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|n| n.asin(angle_mode))
|
||||
}
|
||||
fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|n| n.acos(angle_mode))
|
||||
}
|
||||
fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|n| n.atan(angle_mode))
|
||||
}
|
||||
fn sqrt(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Number::sqrt)
|
||||
}
|
||||
fn log(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Number::log)
|
||||
}
|
||||
fn ln(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Number::ln)
|
||||
}
|
||||
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::add),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::add),
|
||||
}
|
||||
}
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::sub),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::sub),
|
||||
}
|
||||
}
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_matrix) => Matrix::from(&[Entry::Vector(self.clone())])?.mul(arg),
|
||||
Entry::Vector(v2) => {
|
||||
if self.values.len() != v2.values.len() {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
match (self.direction, v2.direction) {
|
||||
(VectorDirection::Row, VectorDirection::Column) => {
|
||||
// Row by column -- will produce a scalar
|
||||
self.values
|
||||
.iter()
|
||||
.zip(v2.values.iter())
|
||||
.try_fold(Entry::Number(Number::ZERO), |acc, (n1, n2)| {
|
||||
acc.add(&n1.mul(&Entry::Number(*n2))?)
|
||||
})
|
||||
}
|
||||
(VectorDirection::Column, VectorDirection::Row) => {
|
||||
// TODO: Do we need to clone?
|
||||
Matrix::from(&[Entry::Vector(self.clone())])?
|
||||
.mul(&Matrix::from(&[arg.clone()])?)
|
||||
}
|
||||
(VectorDirection::Row, VectorDirection::Row)
|
||||
| (VectorDirection::Column, VectorDirection::Column) => {
|
||||
Err(CalculatorError::DimensionMismatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::mul),
|
||||
}
|
||||
}
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::div),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::div),
|
||||
}
|
||||
}
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::int_divide),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::int_divide),
|
||||
}
|
||||
}
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::modulo),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::modulo),
|
||||
}
|
||||
}
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::pow),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::pow),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
pub fn from(entries: &[Entry]) -> CalculatorResult<Entry> {
|
||||
if entries.is_empty() {
|
||||
return Err(CalculatorError::NotEnoughStackEntries);
|
||||
}
|
||||
Self {
|
||||
values: entries
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
Entry::Matrix(_) | Entry::Vector(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => Ok(*number),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Number>>>()?,
|
||||
direction: VectorDirection::default(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
|
||||
fn iterated_unary(
|
||||
&self,
|
||||
op: impl Fn(&Number) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Vector(Self {
|
||||
values: self
|
||||
.values
|
||||
.iter()
|
||||
.map(|n| op(n))
|
||||
.map(|e| match e {
|
||||
// Only numbers are valid in a vector
|
||||
Ok(Entry::Number(number)) => Ok(number),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Number>>>()?,
|
||||
direction: self.direction,
|
||||
}))
|
||||
}
|
||||
|
||||
fn iterated_binary_vec(
|
||||
&self,
|
||||
v2: &Self,
|
||||
op: impl Fn(&Number, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
if self.values.len() != v2.values.len() {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
if self.direction != v2.direction {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
Ok(Entry::Vector(Self {
|
||||
values: self
|
||||
.values
|
||||
.iter()
|
||||
.zip(v2.values.iter())
|
||||
.map(|(n1, n2)| op(n1, &Entry::Number(*n2)))
|
||||
.map(|e| match e {
|
||||
// Only numbers are valid in a vector
|
||||
Ok(Entry::Number(number)) => Ok(number),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Number>>>()?,
|
||||
direction: self.direction,
|
||||
}))
|
||||
}
|
||||
fn iterated_binary_num(
|
||||
&self,
|
||||
n2: &Number,
|
||||
op: impl Fn(&Number, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Vector(Self {
|
||||
values: self
|
||||
.values
|
||||
.iter()
|
||||
.map(|n| op(n, &Entry::Number(*n2)))
|
||||
.map(|e| match e {
|
||||
// Only numbers are valid in a vector
|
||||
Ok(Entry::Number(number)) => Ok(number),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Number>>>()?,
|
||||
direction: self.direction,
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Vector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[{}]",
|
||||
self.values
|
||||
.iter()
|
||||
.map(|number| format!("{}", number))
|
||||
.collect::<Vec<String>>()
|
||||
.join("; ")
|
||||
)
|
||||
}
|
||||
}
|
@ -470,7 +470,7 @@ struct ClippyRectangle<'a> {
|
||||
}
|
||||
|
||||
impl ClippyRectangle<'_> {
|
||||
// TODO: Make this static somehow
|
||||
// Cannot be static since the clippy rectangle's text can change
|
||||
fn size(&self) -> Dimensions {
|
||||
let (width, height) = self.msg.lines().fold((0, 0), |(width, height), l| {
|
||||
(cmp::max(width, l.len()), height + 1)
|
||||
|
Loading…
Reference in New Issue
Block a user