diff --git a/cli/src/main.rs b/cli/src/main.rs index fe052d5..16818d4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,7 @@ mod output; mod repl; +use kalk::kalk_value::ScientificNotationFormat; use kalk::parser; use seahorse::{App, Context, Flag, FlagType}; use std::env; @@ -24,6 +25,10 @@ fn main() { .description("Specify number precision") .alias("p"), ) + .flag( + Flag::new("eng", FlagType::Bool) + .description("Engineering mode") + ) .flag( Flag::new("angle-unit", FlagType::String) .description("Unit used for angles, either rad or deg. This can also be specified using an environment variable with the name 'ANGLE_UNIT'.") @@ -58,6 +63,12 @@ fn default_action(context: &Context) { let precision = context .int_flag("precision") .unwrap_or(output::DEFAULT_PRECISION as isize) as u32; + let format = if context.bool_flag("eng") { + ScientificNotationFormat::Engineering + } else { + ScientificNotationFormat::Normal + }; + if let Ok(max_recursion_depth) = context.int_flag("max-recursion-depth") { parser_context = parser_context.set_max_recursion_depth(max_recursion_depth as u32); } @@ -72,7 +83,7 @@ fn default_action(context: &Context) { if context.args.is_empty() { // REPL - repl::start(&mut parser_context, precision); + repl::start(&mut parser_context, precision, format); } else { // Direct output output::eval( @@ -80,6 +91,7 @@ fn default_action(context: &Context) { &context.args.join(" "), precision, 10u8, + format, ); } } diff --git a/cli/src/output.rs b/cli/src/output.rs index 17c66f0..6bbf442 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -1,9 +1,9 @@ use ansi_term::Colour::Red; -use kalk::parser; +use kalk::{kalk_value::ScientificNotationFormat, parser}; pub(crate) const DEFAULT_PRECISION: u32 = 63; -pub fn eval(parser: &mut parser::Context, input: &str, precision: u32, base: u8) { +pub fn eval(parser: &mut parser::Context, input: &str, precision: u32, base: u8, format: ScientificNotationFormat) { match parser::eval(parser, input, precision) { Ok(Some(mut result)) => { if !result.set_radix(base) { @@ -13,7 +13,7 @@ pub fn eval(parser: &mut parser::Context, input: &str, precision: u32, base: u8) } if precision == DEFAULT_PRECISION { - println!("{}", result.to_string_pretty()); + println!("{}", result.to_string_pretty_format(format)); return; } diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 94e4f6a..e2f789d 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -1,5 +1,6 @@ use crate::output; use ansi_term::Colour::{self, Cyan}; +use kalk::kalk_value::ScientificNotationFormat; use kalk::parser; use lazy_static::lazy_static; use regex::Captures; @@ -24,7 +25,7 @@ struct Context { base: u8, } -pub fn start(parser: &mut parser::Context, precision: u32) { +pub fn start(parser: &mut parser::Context, precision: u32, format: ScientificNotationFormat) { let mut editor = Editor::::new(); editor.set_helper(Some(RLHelper { highlighter: LineHighlighter {}, @@ -66,7 +67,7 @@ pub fn start(parser: &mut parser::Context, precision: u32) { match readline { Ok(input) => { editor.add_history_entry(input.as_str()); - eval_repl(&mut repl, parser, &input, precision); + eval_repl(&mut repl, parser, &input, precision, format); } Err(ReadlineError::Interrupted) => break, _ => break, @@ -78,7 +79,7 @@ pub fn start(parser: &mut parser::Context, precision: u32) { } } -fn eval_repl(repl: &mut self::Context, parser: &mut parser::Context, input: &str, precision: u32) { +fn eval_repl(repl: &mut self::Context, parser: &mut parser::Context, input: &str, precision: u32, format: ScientificNotationFormat) { if let Some(file_name) = input.strip_prefix("load ") { if let Some(file_path) = crate::get_input_file_by_name(file_name) { crate::load_input_file(&file_path, precision, parser); @@ -109,7 +110,7 @@ fn eval_repl(repl: &mut self::Context, parser: &mut parser::Context, input: &str "clear" => print!("\x1B[2J"), "exit" => process::exit(0), "help" => print_cli_help(), - _ => output::eval(parser, input, precision, repl.base), + _ => output::eval(parser, input, precision, repl.base, format), } } diff --git a/kalk/src/calculation_result.rs b/kalk/src/calculation_result.rs index 57b3a74..67b9cc4 100644 --- a/kalk/src/calculation_result.rs +++ b/kalk/src/calculation_result.rs @@ -1,6 +1,6 @@ use wasm_bindgen::prelude::wasm_bindgen; -use crate::kalk_value::{ComplexNumberType, KalkValue, ScientificNotation}; +use crate::kalk_value::{ComplexNumberType, KalkValue, ScientificNotation, ScientificNotationFormat}; #[wasm_bindgen] pub struct CalculationResult { @@ -36,15 +36,15 @@ impl CalculationResult { self.value.to_string_big() } - #[wasm_bindgen(js_name = toPrettyString)] - pub fn to_string_pretty(&self) -> String { + #[wasm_bindgen(js_name = toPrettyStringWithFormat)] + pub fn to_string_pretty_format(&self, format: ScientificNotationFormat) -> String { let value = if self.radix == 10 { - self.value.to_string_pretty_radix(10) + self.value.to_string_pretty_radix(10, format) } else { format!( "{}\n{}", - self.value.to_string_pretty_radix(10), - self.value.to_string_pretty_radix(self.radix), + self.value.to_string_pretty_radix(10, format), + self.value.to_string_pretty_radix(self.radix, format), ) }; @@ -55,6 +55,11 @@ impl CalculationResult { } } + #[wasm_bindgen(js_name = toPrettyString)] + pub fn to_string_pretty(&self) -> String { + self.to_string_pretty_format(ScientificNotationFormat::Normal) + } + #[wasm_bindgen(js_name = getValue)] pub fn to_f64(&self) -> f64 { self.value.to_f64() diff --git a/kalk/src/kalk_value/mod.rs b/kalk/src/kalk_value/mod.rs index 2ee993f..2d40d2f 100644 --- a/kalk/src/kalk_value/mod.rs +++ b/kalk/src/kalk_value/mod.rs @@ -2,7 +2,7 @@ pub mod with_rug; #[cfg(feature = "rug")] -use rug::Float; +use rug::{Float, ops::Pow}; #[cfg(feature = "rug")] pub use with_rug::*; @@ -108,7 +108,6 @@ macro_rules! as_number_or_zero { #[wasm_bindgen] #[derive(Clone)] pub struct ScientificNotation { - pub negative: bool, pub value: f64, pub exponent: i32, pub imaginary: bool, @@ -121,17 +120,42 @@ pub enum ComplexNumberType { Imaginary, } +#[wasm_bindgen] +#[derive(Clone, Copy)] +pub enum ScientificNotationFormat { + Normal, + Engineering, +} + #[wasm_bindgen] impl ScientificNotation { #[wasm_bindgen(js_name = toString)] pub fn to_js_string(&self) -> String { self.to_string() } + + pub fn to_string_format(&self, format: ScientificNotationFormat) -> String { + match format { + ScientificNotationFormat::Normal => self.to_string(), + ScientificNotationFormat::Engineering => self.to_string_eng(), + } + } + + fn to_string_eng(&self) -> String { + let exponent = self.exponent - 1; + let modulo = exponent % 3; + let value = self.value * 10_f64.powi(modulo); + + ScientificNotation { + value, + exponent: exponent - modulo + 1, + imaginary: self.imaginary, + }.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 { @@ -140,11 +164,10 @@ impl std::fmt::Display for ScientificNotation { write!( f, - "{}{}10^{} {}", - sign, + "{}10^{}{}", digits_and_mul, self.exponent - 1, - if self.imaginary { "i" } else { "" } + if self.imaginary { " i" } else { "" } ) } } @@ -288,7 +311,7 @@ impl KalkValue { } } - pub fn to_string_pretty_radix(&self, radix: u8) -> String { + pub fn to_string_pretty_radix(&self, radix: u8, format: ScientificNotationFormat) -> String { let (real, imaginary, unit) = match self { KalkValue::Number(real, imaginary, unit) => (real, imaginary, unit), _ => return self.to_string(), @@ -308,21 +331,31 @@ impl KalkValue { 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 { + let is_engineering_mode = matches!(format, ScientificNotationFormat::Engineering); + let result_str = if is_engineering_mode { + has_scientific_notation = true; + + sci_notation_real.to_string_format(ScientificNotationFormat::Engineering) + } else 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() + sci_notation_real.to_string_format(format) } 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) + let result_str_imaginary = if is_engineering_mode { + has_scientific_notation = true; + + sci_notation_imaginary.to_string_format(ScientificNotationFormat::Engineering) + } else if (-6..8).contains(&sci_notation_imaginary.exponent) || imaginary == &0f64 || imaginary == &1f64 { @@ -333,7 +366,7 @@ impl KalkValue { } else if radix == 10 { has_scientific_notation = true; - sci_notation_imaginary.to_string().trim().to_string() + sci_notation_imaginary.to_string_format(format) } else { return String::new(); }; @@ -368,15 +401,15 @@ impl KalkValue { if estimate != output && radix == 10 { output.push_str(&format!(" ≈ {}", estimate)); } - } else if has_scientific_notation { + } else if has_scientific_notation && !is_engineering_mode { output.insert_str(0, &format!("{} ≈ ", self)); } output } - pub fn to_string_pretty(&self) -> String { - self.to_string_pretty_radix(10) + pub fn to_string_pretty(&self, format: ScientificNotationFormat) -> String { + self.to_string_pretty_radix(10, format) } pub fn to_string_with_unit(&self) -> String { @@ -516,7 +549,6 @@ impl KalkValue { 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, @@ -1309,7 +1341,6 @@ fn pow(x: f64, y: f64) -> f64 { #[cfg(feature = "rug")] fn pow(x: Float, y: Float) -> Float { - use rug::ops::Pow; x.pow(y) } @@ -1377,9 +1408,11 @@ impl From for KalkValue { #[cfg(test)] mod tests { - use crate::kalk_value::{spaced, KalkValue}; + use crate::kalk_value::{spaced, KalkValue, ScientificNotationFormat}; use crate::test_helpers::cmp; + use super::ScientificNotation; + #[test] fn test_spaced() { assert_eq!(spaced("1"), String::from("1")); @@ -1543,8 +1576,30 @@ mod tests { (float!(3.00000000004), float!(0.0), "3"), ]; for (real, imaginary, output) in in_out { - let result = KalkValue::Number(real, imaginary, None).to_string_pretty(); + let result = KalkValue::Number(real, imaginary, None).to_string_pretty(ScientificNotationFormat::Normal); assert_eq!(output, result); } } + + #[test] + fn test_eng_mode() { + let in_out = vec![ + (1.23, 0, "1.23×10^0"), + (1.23, 1, "12.3×10^0"), + (1.23, 2, "123×10^0"), + (1.23, 3, "1.23×10^3"), + (1.23, 4, "12.3×10^3"), + (1.23, 5, "123×10^3"), + (1.23, 6, "1.23×10^6"), + ]; + for (value, exponent, output) in in_out { + let sci = ScientificNotation { + value, + exponent: exponent + 1, + imaginary: false, + }; + + assert_eq!(sci.to_string_format(ScientificNotationFormat::Engineering), output); + } + } }