Start work on complex types

This commit is contained in:
Austen Adler 2021-05-30 16:31:13 -04:00
parent 4b0e6e7e10
commit 445ae3f535
6 changed files with 1339 additions and 963 deletions

File diff suppressed because it is too large Load Diff

336
src/calc/entries.rs Normal file
View File

@ -0,0 +1,336 @@
// use super::operations::CalculatorStateChange;
use super::errors::CalculatorResult;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Number {
pub value: f64,
}
// #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
// pub struct Vector {
// pub value: Vec<Number>,
// }
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum Entry {
Number(Number),
// Vector(Vector),
// Matrix(Vec<Vec<f64>>),
}
impl CalculatorEntry for Entry {
fn is_valid(&self) -> bool {
match self {
Entry::Number(number) => number.is_valid(),
// Entry::Vector(vector) => vector.add(),
}
}
fn add(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.add(arg),
// Entry::Vector(vector) => vector.add(),
}
}
fn sub(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.sub(arg),
// Entry::Vector(vector) => vector.sub(),
}
}
fn mul(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.mul(arg),
// Entry::Vector(vector) => vector.mul(),
}
}
fn div(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.div(arg),
// Entry::Vector(vector) => vector.div(),
}
}
fn int_divide(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.int_divide(arg),
// Entry::Vector(vector) => vector.int_divide(),
}
}
fn negate(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.negate(),
// Entry::Vector(vector) => vector.negate(),
}
}
fn abs(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.abs(),
// Entry::Vector(vector) => vector.abs(),
}
}
fn inverse(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.inverse(),
// Entry::Vector(vector) => vector.inverse(),
}
}
fn modulo(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.modulo(arg),
// Entry::Vector(vector) => vector.modulo(),
}
}
fn sin(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.sin(),
// Entry::Vector(vector) => vector.sin(),
}
}
fn cos(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.cos(),
// Entry::Vector(vector) => vector.cos(),
}
}
fn tan(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.tan(),
// Entry::Vector(vector) => vector.tan(),
}
}
fn asin(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.asin(),
// Entry::Vector(vector) => vector.asin(),
}
}
fn acos(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.acos(),
// Entry::Vector(vector) => vector.acos(),
}
}
fn atan(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.atan(),
// Entry::Vector(vector) => vector.atan(),
}
}
fn sqrt(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.sqrt(),
// Entry::Vector(vector) => vector.sqrt(),
}
}
fn log(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.log(),
// Entry::Vector(vector) => vector.log(),
}
}
fn ln(&self) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.ln(),
// Entry::Vector(vector) => vector.ln(),
}
}
fn pow(&self, arg: Entry) -> CalculatorResult<Entry> {
match self {
Entry::Number(number) => number.pow(arg),
// Entry::Vector(vector) => vector.pow(),
}
}
}
impl CalculatorEntry for Number {
fn is_valid(&self) -> bool {
!self.value.is_nan() && !self.value.is_infinite()
}
fn add(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64 + self.value,
}))
}
fn sub(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64 - self.value,
}))
}
fn mul(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64 * self.value,
}))
}
fn div(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64 / self.value,
}))
}
fn int_divide(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64.div_euclid(self.value),
}))
}
fn negate(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: -self.value }))
}
fn abs(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: self.value.abs(),
}))
}
fn inverse(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: self.value.recip(),
}))
}
fn modulo(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64 % self.value,
}))
}
fn sin(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: self.value }))
// match self.angle_mode {
// CalculatorAngleMode::Degrees => {
// |self.value: f64| a.to_radians().sin()
// }
// CalculatorAngleMode::Radians => |a: f64| a.sin(),
// CalculatorAngleMode::Grads => |a: f64| {
// (a * std::f64::consts::PI / 200.0).sin()
// },
// }
}
fn cos(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: self.value }))
// match self.angle_mode {
// CalculatorAngleMode::Degrees => {
// |self.value: f64| a.to_radians().cos()
// }
// CalculatorAngleMode::Radians => |a: f64| a.cos(),
// CalculatorAngleMode::Grads => |a: f64| {
// (a * std::f64::consts::PI / 200.0).cos()
// },
// }
}
fn tan(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: self.value }))
// match self.angle_mode {
// CalculatorAngleMode::Degrees => {
// |self.value: f64| a.to_radians().tan()
// }
// CalculatorAngleMode::Radians => |a: f64| a.tan(),
// CalculatorAngleMode::Grads => |a: f64| {
// (a * std::f64::consts::PI / 200.0).tan()
// },
// }
}
fn asin(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: self.value }))
// match self.angle_mode {
// CalculatorAngleMode::Degrees => {
// |self.value: f64| a.asin().to_degrees()
// }
// CalculatorAngleMode::Radians => |a: f64| a.asin(),
// CalculatorAngleMode::Grads => |a: f64| {
// a.asin() * std::f64::consts::PI / 200.0
// },
// }
}
fn acos(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: self.value }))
// match self.angle_mode {
// CalculatorAngleMode::Degrees => {
// |self.value: f64| a.acos().to_degrees()
// }
// CalculatorAngleMode::Radians => |a: f64| a.acos(),
// CalculatorAngleMode::Grads => |a: f64| {
// a.acos() * std::f64::consts::PI / 200.0
// },
// }
}
fn atan(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number { value: self.value }))
// match self.angle_mode {
// CalculatorAngleMode::Degrees => {
// |self.value: f64| a.atan().to_degrees()
// }
// CalculatorAngleMode::Radians => |a: f64| a.atan(),
// CalculatorAngleMode::Grads => |a: f64| {
// a.atan() * std::f64::consts::PI / 200.0
// },
// }
}
fn sqrt(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: self.value.sqrt(),
}))
}
fn log(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: self.value.log10(),
}))
}
fn ln(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: self.value.ln(),
}))
}
fn pow(&self, _arg: Entry) -> CalculatorResult<Entry> {
Ok(Entry::Number(Number {
value: 1.0f64.powf(self.value),
}))
}
// fn e(&self, _arg: Entry) -> CalculatorResult<Entry> {
// Ok(Entry::Number(Number { value:1.0f64 * 10.0_f64.powf(self.value) }))
// }
}
pub trait CalculatorEntry {
fn is_valid(&self) -> bool;
fn add(&self, arg: Entry) -> CalculatorResult<Entry>;
fn sub(&self, arg: Entry) -> CalculatorResult<Entry>;
fn mul(&self, arg: Entry) -> CalculatorResult<Entry>;
fn div(&self, arg: Entry) -> CalculatorResult<Entry>;
fn int_divide(&self, arg: Entry) -> CalculatorResult<Entry>;
fn negate(&self) -> CalculatorResult<Entry>;
fn abs(&self) -> CalculatorResult<Entry>;
fn inverse(&self) -> CalculatorResult<Entry>;
fn modulo(&self, arg: Entry) -> CalculatorResult<Entry>;
fn sin(&self) -> CalculatorResult<Entry>;
fn cos(&self) -> CalculatorResult<Entry>;
fn tan(&self) -> CalculatorResult<Entry>;
fn asin(&self) -> CalculatorResult<Entry>;
fn acos(&self) -> CalculatorResult<Entry>;
fn atan(&self) -> CalculatorResult<Entry>;
fn sqrt(&self) -> CalculatorResult<Entry>;
fn log(&self) -> CalculatorResult<Entry>;
fn ln(&self) -> CalculatorResult<Entry>;
fn pow(&self, arg: Entry) -> CalculatorResult<Entry>;
// fn e(&self, arg: Entry) -> CalculatorResult<Entry>;
}
impl fmt::Display for Entry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Number(Number { value }) => write!(f, "{}", value),
}
}
}
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
// impl fmt::Display for Vector {
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// match self {
// Self::Degrees => write!(f, "DEG"),
// Self::Radians => write!(f, "RAD"),
// Self::Grads => write!(f, "GRD"),
// }
// }
// }

View File

@ -7,65 +7,68 @@ pub type CalculatorResult<T> = Result<T, CalculatorError>;
/// All possible errors the calculator can throw /// All possible errors the calculator can throw
#[derive(Debug)] #[derive(Debug)]
pub enum CalculatorError { pub enum CalculatorError {
/// Divide by zero, log(-1), etc /// Divide by zero, log(-1), etc
ArithmeticError, ArithmeticError,
/// Not enough stck entries for operation /// Not enough stck entries for operation
NotEnoughStackEntries, NotEnoughStackEntries,
/// Thrown when an undo or redo cannot be performed /// Requested type does not match target type
CorruptStateChange(String), TypeMismatch,
/// Cannot undo or redo /// Thrown when an undo or redo cannot be performed
EmptyHistory(String), CorruptStateChange(String),
/// Constant undefined /// Cannot undo or redo
NoSuchConstant(char), EmptyHistory(String),
/// Register undefined /// Constant undefined
NoSuchRegister(char), NoSuchConstant(char),
/// Macro undefined /// Register undefined
NoSuchMacro(char), NoSuchRegister(char),
/// Operator undefined /// Macro undefined
NoSuchOperator(char), NoSuchMacro(char),
/// Setting undefined /// Operator undefined
NoSuchSetting(char), NoSuchOperator(char),
/// Macro calls itself /// Setting undefined
RecursiveMacro(char), NoSuchSetting(char),
/// Could not convert l to number /// Macro calls itself
ParseError, RecursiveMacro(char),
/// Requested precision is too high /// Could not convert l to number
PrecisionTooHigh, ParseError,
/// Config serialization error /// Requested precision is too high
SaveError(Option<ConfyError>), PrecisionTooHigh,
/// Config deserialization error /// Config serialization error
LoadError(Option<ConfyError>), SaveError(Option<ConfyError>),
/// Config deserialization error
LoadError(Option<ConfyError>),
} }
impl error::Error for CalculatorError {} impl error::Error for CalculatorError {}
impl fmt::Display for CalculatorError { impl fmt::Display for CalculatorError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::ArithmeticError => write!(f, "Arithmetic Error"), Self::ArithmeticError => write!(f, "Arithmetic Error"),
Self::NotEnoughStackEntries => write!(f, "Not enough items in the stack"), Self::NotEnoughStackEntries => write!(f, "Not enough items in the stack"),
Self::CorruptStateChange(msg) => { Self::TypeMismatch => write!(f, "Type mismatch"),
write!(f, "Corrupt state change: {}", msg) Self::CorruptStateChange(msg) => {
} write!(f, "Corrupt state change: {}", msg)
Self::EmptyHistory(msg) => write!(f, "No history to {}", msg), }
Self::NoSuchOperator(c) => write!(f, "No such operator '{}'", c), Self::EmptyHistory(msg) => write!(f, "No history to {}", msg),
Self::NoSuchConstant(c) => write!(f, "No such constant '{}'", c), Self::NoSuchOperator(c) => write!(f, "No such operator '{}'", c),
Self::NoSuchRegister(c) => write!(f, "No such register '{}'", c), Self::NoSuchConstant(c) => write!(f, "No such constant '{}'", c),
Self::NoSuchMacro(c) => write!(f, "No such macro '{}'", c), Self::NoSuchRegister(c) => write!(f, "No such register '{}'", c),
Self::NoSuchSetting(c) => write!(f, "No such setting '{}'", c), Self::NoSuchMacro(c) => write!(f, "No such macro '{}'", c),
Self::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c), Self::NoSuchSetting(c) => write!(f, "No such setting '{}'", c),
Self::ParseError => write!(f, "Parse error"), Self::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c),
Self::PrecisionTooHigh => write!(f, "Precision too high"), Self::ParseError => write!(f, "Parse error"),
Self::SaveError(None) => write!(f, "Could not save"), Self::PrecisionTooHigh => write!(f, "Precision too high"),
Self::SaveError(Some(ConfyError::SerializeTomlError(e))) => { Self::SaveError(None) => write!(f, "Could not save"),
write!(f, "Save serialization error: {}", e) Self::SaveError(Some(ConfyError::SerializeTomlError(e))) => {
} write!(f, "Save serialization error: {}", e)
Self::SaveError(Some(e)) => write!(f, "Could not save: {}", e), }
Self::LoadError(None) => write!(f, "Could not load"), Self::SaveError(Some(e)) => write!(f, "Could not save: {}", e),
Self::LoadError(Some(ConfyError::SerializeTomlError(e))) => { Self::LoadError(None) => write!(f, "Could not load"),
write!(f, "Load serialization error: {}", e) Self::LoadError(Some(ConfyError::SerializeTomlError(e))) => {
} write!(f, "Load serialization error: {}", e)
Self::LoadError(Some(e)) => write!(f, "Could not load: {}", e), }
Self::LoadError(Some(e)) => write!(f, "Could not load: {}", e),
}
} }
}
} }

View File

@ -1,3 +1,4 @@
use super::entries::Entry;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Operations that can be sent to the calculator such as +, -, or undo /// Operations that can be sent to the calculator such as +, -, or undo
#[derive(PartialEq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Debug, Serialize, Deserialize)]
@ -12,9 +13,6 @@ pub enum CalculatorOperation {
Modulo, Modulo,
IntegerDivide, IntegerDivide,
//Remainder, //Remainder,
Drop,
Dup,
Swap,
Sin, Sin,
Cos, Cos,
Tan, Tan,
@ -28,7 +26,9 @@ pub enum CalculatorOperation {
// Factorial, // Factorial,
Log, Log,
Ln, Ln,
E, Drop,
Dup,
Swap,
Macro(MacroState), Macro(MacroState),
} }
@ -45,9 +45,9 @@ pub enum OpArgs {
/// This is a macro start and end noop /// This is a macro start and end noop
Macro(MacroState), Macro(MacroState),
/// Operation takes 1 argument, ex: sqrt or negate /// Operation takes 1 argument, ex: sqrt or negate
Unary(f64), Unary(Entry),
/// Operation takes 2 arguments, ex: + or - /// Operation takes 2 arguments, ex: + or -
Binary([f64; 2]), Binary([Entry; 2]),
/// Operation takes no arguments, ex: push /// Operation takes no arguments, ex: push
None, None,
} }

View File

@ -1,3 +1,4 @@
use super::entries::Entry;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
@ -5,38 +6,38 @@ use std::fmt;
/// The calculator state /// The calculator state
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum CalculatorState { pub enum CalculatorState {
Normal, Normal,
WaitingForConstant, WaitingForConstant,
WaitingForMacro, WaitingForMacro,
WaitingForRegister(RegisterState), WaitingForRegister(RegisterState),
WaitingForSetting, WaitingForSetting,
} }
impl Default for CalculatorState { impl Default for CalculatorState {
fn default() -> Self { fn default() -> Self {
Self::Normal Self::Normal
} }
} }
/// The state of the requested register operation /// The state of the requested register operation
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum RegisterState { pub enum RegisterState {
Save, Save,
Load, Load,
} }
/// One calculator constant containing a message and value /// One calculator constant containing a message and value
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalculatorConstant { pub struct CalculatorConstant {
pub help: String, pub help: String,
pub value: f64, pub value: Entry,
} }
/// One calculator macro containing a messsage and value /// One calculator macro containing a messsage and value
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalculatorMacro { pub struct CalculatorMacro {
pub help: String, pub help: String,
pub value: String, pub value: String,
} }
/// Map of chars to constants /// Map of chars to constants
@ -46,264 +47,269 @@ pub type CalculatorConstants = HashMap<char, CalculatorConstant>;
pub type CalculatorMacros = HashMap<char, CalculatorMacro>; pub type CalculatorMacros = HashMap<char, CalculatorMacro>;
/// Map of chars to registers /// Map of chars to registers
pub type CalculatorRegisters = HashMap<char, f64>; pub type CalculatorRegisters = HashMap<char, Entry>;
/// Possible calculator angle modes /// Possible calculator angle modes
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "angle_mode")] #[serde(tag = "angle_mode")]
pub enum CalculatorAngleMode { pub enum CalculatorAngleMode {
Degrees, Degrees,
Radians, Radians,
Grads, Grads,
} }
impl Default for CalculatorAngleMode { impl Default for CalculatorAngleMode {
fn default() -> Self { fn default() -> Self {
Self::Degrees Self::Degrees
} }
} }
impl fmt::Display for CalculatorAngleMode { impl fmt::Display for CalculatorAngleMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::Degrees => write!(f, "DEG"), Self::Degrees => write!(f, "DEG"),
Self::Radians => write!(f, "RAD"), Self::Radians => write!(f, "RAD"),
Self::Grads => write!(f, "GRD"), Self::Grads => write!(f, "GRD"),
}
} }
}
} }
/// The calculator digit display mode /// The calculator digit display mode
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "display_mode")] #[serde(tag = "display_mode")]
pub enum CalculatorDisplayMode { pub enum CalculatorDisplayMode {
/// Rust's default f64 format /// Rust's default Entry format
Default, Default,
/// Thousands separator /// Thousands separator
Separated { separator: char }, Separated { separator: char },
/// Aligned scientific format /// Aligned scientific format
Scientific { precision: usize }, Scientific { precision: usize },
/// Scientific format, chunked by groups of 3 /// Scientific format, chunked by groups of 3
/// ///
/// Example: 1 E+5 or 100E+5 /// Example: 1 E+5 or 100E+5
Engineering { precision: usize }, Engineering { precision: usize },
/// Fixed precision /// Fixed precision
Fixed { precision: usize }, Fixed { precision: usize },
} }
impl fmt::Display for CalculatorDisplayMode { impl fmt::Display for CalculatorDisplayMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::Default => write!(f, "DEF"), Self::Default => write!(f, "DEF"),
Self::Separated { separator } => write!(f, "SEP({})", separator), Self::Separated { separator } => write!(f, "SEP({})", separator),
Self::Scientific { precision } => write!(f, "SCI({})", precision), Self::Scientific { precision } => write!(f, "SCI({})", precision),
Self::Engineering { precision } => write!(f, "ENG({})", precision), Self::Engineering { precision } => write!(f, "ENG({})", precision),
Self::Fixed { precision } => write!(f, "FIX({})", precision), Self::Fixed { precision } => write!(f, "FIX({})", precision),
}
} }
}
} }
impl Default for CalculatorDisplayMode { impl Default for CalculatorDisplayMode {
fn default() -> Self { fn default() -> Self {
Self::Default Self::Default
} }
} }
impl CalculatorDisplayMode { impl CalculatorDisplayMode {
pub fn format_number(&self, number: f64) -> String { pub fn format_number(&self, number: &Entry) -> String {
match self { match self {
Self::Default => format!("{}", number), Self::Default => format!("{}", number),
Self::Separated { separator } => Self::separated(number, *separator), Self::Separated { separator } => Self::separated(number, *separator),
Self::Scientific { precision } => Self::scientific(number, *precision), Self::Scientific { precision } => Self::scientific(number, *precision),
Self::Engineering { precision } => Self::engineering(number, *precision), Self::Engineering { precision } => Self::engineering(number, *precision),
Self::Fixed { precision } => { Self::Fixed { precision } => {
format!("{:0>.precision$}", number, precision = precision) format!("{:0>.precision$}", number, precision = precision)
} }
}
} }
}
// Based on https://stackoverflow.com/a/65266882 // Based on https://stackoverflow.com/a/65266882
fn scientific(f: f64, precision: usize) -> String { fn scientific(_f: &Entry, _precision: usize) -> String {
let mut ret = format!("{:.precision$E}", f, precision = precision); // TODO
let exp = ret.split_off(ret.find('E').unwrap_or(0)); String::from("TODO")
let (exp_sign, exp) = exp // let mut ret = format!("{:.precision$E}", f, precision = precision);
.strip_prefix("E-") // let exp = ret.split_off(ret.find('E').unwrap_or(0));
.map_or_else(|| ('+', &exp[1..]), |stripped| ('-', stripped)); // let (exp_sign, exp) = exp
// .strip_prefix("E-")
// .map_or_else(|| ('+', &exp[1..]), |stripped| ('-', stripped));
let sign = if ret.starts_with('-') { "" } else { " " }; // let sign = if ret.starts_with('-') { "" } else { " " };
format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2) // format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2)
} }
fn engineering(f: f64, precision: usize) -> String { fn engineering(_f: &Entry, _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 // TODO
// 1,000 => 1000E3 String::from("TODO")
let all = format!(" {:.precision$E}", f, precision = precision) /*// 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
// Remove . since it can be moved // 1,000 => 1000E3
.replacen(".", "", 1) let all = format!(" {:.precision$E}", f, precision = precision)
// Add 00E before E here so the length is enough for slicing below // Remove . since it can be moved
.replacen("E", "00E", 1); .replacen(".", "", 1)
// Extract mantissa and the string representation of the exponent. Unwrap should be safe as formatter will insert E // Add 00E before E here so the length is enough for slicing below
// 1000E3 => (1000, E3) .replacen("E", "00E", 1);
let (num_str, exp_str) = all.split_at(all.find('E').unwrap()); // Extract mantissa and the string representation of the exponent. Unwrap should be safe as formatter will insert E
// Extract the exponent as an isize. This should always be true because f64 max will be ~400 // 1000E3 => (1000, E3)
// E3 => 3 as isize let (num_str, exp_str) = all.split_at(all.find('E').unwrap());
let exp = exp_str[1..].parse::<isize>().unwrap(); // Extract the exponent as an isize. This should always be true because Entry max will be ~400
// Sign of the exponent. If string representation starts with E-, then negative // E3 => 3 as isize
let display_exp_sign = if exp_str.strip_prefix("E-").is_some() { let exp = exp_str[1..].parse::<isize>().unwrap();
'-' // Sign of the exponent. If string representation starts with E-, then negative
} else { 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(); // The exponent to display. Always a multiple of 3 in engineering mode. Always positive because sign is added with display_exp_sign above
// Number of whole digits. Always 1, 2, or 3 depending on exponent divisibility // 100 => 0, 1000 => 3, .1 => 3 (but will show as -3)
let num_whole_digits = exp.rem_euclid(3) as usize + 1; let display_exp = (exp.div_euclid(3) * 3).abs();
// Number of whole digits. Always 1, 2, or 3 depending on exponent divisibility
// If this is a negative number, strip off the added space, otherwise keep the space (and next digit) let num_whole_digits = exp.rem_euclid(3) as usize + 1;
let num_str = if num_str.strip_prefix(" -").is_some() {
&num_str[1..] // If this is a negative number, strip off the added space, otherwise keep the space (and next digit)
} else { let num_str = if num_str.strip_prefix(" -").is_some() {
num_str &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]; // 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)
// 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 // Original number is 1,000 => whole will be 1, if original is 0.01, whole will be 10
let decimal = &num_str[(num_whole_digits + 1)..=(precision + num_whole_digits)]; let whole = &num_str[0..=num_whole_digits];
// Right align whole portion, always have decimal point // 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
format!( let decimal = &num_str[(num_whole_digits + 1)..=(precision + num_whole_digits)];
"{: >4}.{} E{}{:0>pad$}", // Right align whole portion, always have decimal point
// display_sign, format!(
whole, "{: >4}.{} E{}{:0>pad$}",
decimal, // display_sign,
display_exp_sign, whole,
display_exp, decimal,
pad = 2 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()); fn separated(f: &Entry, sep: char) -> String {
for i in 0..((end - start - 1).div_euclid(3)) { let mut ret = f.to_string();
ret.insert(end - (i + 1) * 3, sep); 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
} }
ret
}
} }
/// Left or right calculator alignment /// Left or right calculator alignment
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CalculatorAlignment { pub enum CalculatorAlignment {
Right, Right,
Left, Left,
} }
impl Default for CalculatorAlignment { impl Default for CalculatorAlignment {
fn default() -> Self { fn default() -> Self {
Self::Left Self::Left
} }
} }
impl fmt::Display for CalculatorAlignment { impl fmt::Display for CalculatorAlignment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::Left => write!(f, "L"), Self::Left => write!(f, "L"),
Self::Right => write!(f, "R"), Self::Right => write!(f, "R"),
}
} }
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_scientific() { fn test_scientific() {
for (f, precision, s) in vec![ for (f, precision, s) in vec![
// Basic // Basic
(1.0, 0, " 1 E+00"), (1.0, 0, " 1 E+00"),
(-1.0, 0, "-1 E+00"), (-1.0, 0, "-1 E+00"),
(100.0, 0, " 1 E+02"), (100.0, 0, " 1 E+02"),
(0.1, 0, " 1 E-01"), (0.1, 0, " 1 E-01"),
(0.01, 0, " 1 E-02"), (0.01, 0, " 1 E-02"),
(-0.1, 0, "-1 E-01"), (-0.1, 0, "-1 E-01"),
// i // i
(1.0, 0, " 1 E+00"), (1.0, 0, " 1 E+00"),
// Precision // Precision
(-0.123456789, 3, "-1.235 E-01"), (-0.123_456_789, 3, "-1.235 E-01"),
(-0.123456789, 2, "-1.23 E-01"), (-0.123_456_789, 2, "-1.23 E-01"),
(-0.123456789, 2, "-1.23 E-01"), (-0.123_456_789, 2, "-1.23 E-01"),
(-1e99, 2, "-1.00 E+99"), (-1e99, 2, "-1.00 E+99"),
(-1e100, 2, "-1.00 E+100"), (-1e100, 2, "-1.00 E+100"),
// Rounding // Rounding
(0.5, 2, " 5.00 E-01"), (0.5, 2, " 5.00 E-01"),
(0.5, 1, " 5.0 E-01"), (0.5, 1, " 5.0 E-01"),
(0.5, 0, " 5 E-01"), (0.5, 0, " 5 E-01"),
(1.5, 2, " 1.50 E+00"), (1.5, 2, " 1.50 E+00"),
(1.5, 1, " 1.5 E+00"), (1.5, 1, " 1.5 E+00"),
(1.5, 0, " 2 E+00"), (1.5, 0, " 2 E+00"),
] { ] {
assert_eq!( assert_eq!(
CalculatorDisplayMode::Scientific { precision }.format_number(f), CalculatorDisplayMode::Scientific { precision }.format_number(f),
s s
); );
}
} }
}
#[test] #[test]
fn test_separated() { fn test_separated() {
for (f, separator, s) in vec![ for (f, separator, s) in vec![
(100.0, ',', "100"), (100.0, ',', "100"),
(100.0, ',', "100"), (100.0, ',', "100"),
(-100.0, ',', "-100"), (-100.0, ',', "-100"),
(1_000.0, ',', "1,000"), (1_000.0, ',', "1,000"),
(-1_000.0, ',', "-1,000"), (-1_000.0, ',', "-1,000"),
(10_000.0, ',', "10,000"), (10_000.0, ',', "10,000"),
(-10_000.0, ',', "-10,000"), (-10_000.0, ',', "-10,000"),
(100_000.0, ',', "100,000"), (100_000.0, ',', "100,000"),
(-100_000.0, ',', "-100,000"), (-100_000.0, ',', "-100,000"),
(1_000_000.0, ',', "1,000,000"), (1_000_000.0, ',', "1,000,000"),
(-1_000_000.0, ',', "-1,000,000"), (-1_000_000.0, ',', "-1,000,000"),
(1_000_000.123456789, ',', "1,000,000.123456789"), (1_000_000.123_456_789, ',', "1,000,000.123456789"),
(-1_000_000.123456789, ',', "-1,000,000.123456789"), (-1_000_000.123_456_789, ',', "-1,000,000.123456789"),
(1_000_000.123456789, ' ', "1 000 000.123456789"), (1_000_000.123_456_789, ' ', "1 000 000.123456789"),
(1_000_000.123456789, ' ', "1 000 000.123456789"), (1_000_000.123_456_789, ' ', "1 000 000.123456789"),
] { ] {
assert_eq!( assert_eq!(
CalculatorDisplayMode::Separated { separator }.format_number(f), CalculatorDisplayMode::Separated { separator }.format_number(f),
s s
); );
}
} }
}
#[test] #[test]
fn test_engineering() { fn test_engineering() {
for (f, precision, s) in vec![ for (f, precision, s) in vec![
(100.0, 3, " 100.000 E+00"), (100.0, 3, " 100.000 E+00"),
(100.0, 3, " 100.000 E+00"), (100.0, 3, " 100.000 E+00"),
(-100.0, 3, "-100.000 E+00"), (-100.0, 3, "-100.000 E+00"),
(100.0, 0, " 100. E+00"), (100.0, 0, " 100. E+00"),
(-100.0, 0, "-100. E+00"), (-100.0, 0, "-100. E+00"),
(0.1, 2, " 100.00 E-03"), (0.1, 2, " 100.00 E-03"),
(0.01, 2, " 10.00 E-03"), (0.01, 2, " 10.00 E-03"),
(0.001, 2, " 1.00 E-03"), (0.001, 2, " 1.00 E-03"),
(0.0001, 2, " 100.00 E-06"), (0.0001, 2, " 100.00 E-06"),
// Rounding // Rounding
(0.5, 2, " 500.00 E-03"), (0.5, 2, " 500.00 E-03"),
(0.5, 1, " 500.0 E-03"), (0.5, 1, " 500.0 E-03"),
(0.5, 0, " 500. E-03"), (0.5, 0, " 500. E-03"),
(1.5, 2, " 1.50 E+00"), (1.5, 2, " 1.50 E+00"),
(1.5, 1, " 1.5 E+00"), (1.5, 1, " 1.5 E+00"),
(1.5, 0, " 2. E+00"), (1.5, 0, " 2. E+00"),
] { ] {
assert_eq!( assert_eq!(
CalculatorDisplayMode::Engineering { precision }.format_number(f), CalculatorDisplayMode::Engineering { precision }.format_number(f),
s s
); );
}
} }
}
} }

View File

@ -113,7 +113,7 @@ impl App {
"{}: {} ({})", "{}: {} ({})",
key, key,
constant.help, constant.help,
self.calculator.display_mode.format_number(constant.value) self.calculator.display_mode.format_number(&constant.value)
) )
}) })
.fold(String::new(), |acc, s| acc + &s + "\n") .fold(String::new(), |acc, s| acc + &s + "\n")
@ -233,9 +233,11 @@ impl App {
.enumerate() .enumerate()
.rev() .rev()
.map(|(i, m)| { .map(|(i, m)| {
let number = self.calculator.display_mode.format_number(*m); let number = self.calculator.display_mode.format_number(&*m);
let content = match self.calculator.calculator_alignment { let content = match self.calculator.calculator_alignment {
CalculatorAlignment::Left => format!("{:>2}: {}", i, number), CalculatorAlignment::Left => {
format!("{:>2}: {}", i, number)
}
CalculatorAlignment::Right => { CalculatorAlignment::Right => {
let ret = format!("{} :{:>2}", number, i); let ret = format!("{} :{:>2}", number, i);
if ret.len() < chunk.width.saturating_sub(BORDER_SIZE) as usize { if ret.len() < chunk.width.saturating_sub(BORDER_SIZE) as usize {