Split format logic into another mod
This commit is contained in:
parent
c0cd93d0a5
commit
df59e45b80
155
src/format.rs
Normal file
155
src/format.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Based on https://stackoverflow.com/a/65266882
|
||||||
|
pub 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) = if let Some(stripped) = exp.strip_prefix("E-") {
|
||||||
|
('-', stripped)
|
||||||
|
} else {
|
||||||
|
('+', &exp[1..])
|
||||||
|
};
|
||||||
|
|
||||||
|
let sign = if !ret.starts_with('-') { " " } else { "" };
|
||||||
|
format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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 f64 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 let Some(stripped) = exp_str.strip_prefix("E-") {
|
||||||
|
'-'
|
||||||
|
} 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 + 1)];
|
||||||
|
// 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 + 1)];
|
||||||
|
// Right align whole portion, always have decimal point
|
||||||
|
format!(
|
||||||
|
"{: >4}.{} E{}{:0>pad$}",
|
||||||
|
// display_sign,
|
||||||
|
whole,
|
||||||
|
decimal,
|
||||||
|
display_exp_sign,
|
||||||
|
display_exp,
|
||||||
|
pad = 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_scientific() {
|
||||||
|
for (f, p, s) in vec![
|
||||||
|
// Basic
|
||||||
|
(1.0, 0, " 1 E+00"),
|
||||||
|
(-1.0, 0, "-1 E+00"),
|
||||||
|
(100.0, 0, " 1 E+02"),
|
||||||
|
(0.1, 0, " 1 E-01"),
|
||||||
|
(0.01, 0, " 1 E-02"),
|
||||||
|
(-0.1, 0, "-1 E-01"),
|
||||||
|
// i
|
||||||
|
(1.0, 0, " 1 E+00"),
|
||||||
|
// Precision
|
||||||
|
(-0.123456789, 3, "-1.235 E-01"),
|
||||||
|
(-0.123456789, 2, "-1.23 E-01"),
|
||||||
|
(-0.123456789, 2, "-1.23 E-01"),
|
||||||
|
(-1e99, 2, "-1.00 E+99"),
|
||||||
|
(-1e100, 2, "-1.00 E+100"),
|
||||||
|
// Rounding
|
||||||
|
(0.5, 2, " 5.00 E-01"),
|
||||||
|
(0.5, 1, " 5.0 E-01"),
|
||||||
|
(0.5, 0, " 5 E-01"),
|
||||||
|
(1.5, 2, " 1.50 E+00"),
|
||||||
|
(1.5, 1, " 1.5 E+00"),
|
||||||
|
(1.5, 0, " 2 E+00"),
|
||||||
|
] {
|
||||||
|
assert_eq!(fmt_scientific(f, p), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_separated() {
|
||||||
|
for (f, c, s) in vec![
|
||||||
|
(100.0, ',', "100"),
|
||||||
|
(100.0, ',', "100"),
|
||||||
|
(-100.0, ',', "-100"),
|
||||||
|
(1_000.0, ',', "1,000"),
|
||||||
|
(-1_000.0, ',', "-1,000"),
|
||||||
|
(10_000.0, ',', "10,000"),
|
||||||
|
(-10_000.0, ',', "-10,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.123456789, ',', "1,000,000.123456789"),
|
||||||
|
(-1_000_000.123456789, ',', "-1,000,000.123456789"),
|
||||||
|
(1_000_000.123456789, ' ', "1 000 000.123456789"),
|
||||||
|
(1_000_000.123456789, ' ', "1 000 000.123456789"),
|
||||||
|
] {
|
||||||
|
assert_eq!(fmt_separated(f, c), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_engineering() {
|
||||||
|
for (f, c, 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, 0, " 100. E+00"),
|
||||||
|
(-100.0, 0, "-100. E+00"),
|
||||||
|
(0.1, 2, " 100.00 E-03"),
|
||||||
|
(0.01, 2, " 10.00 E-03"),
|
||||||
|
(0.001, 2, " 1.00 E-03"),
|
||||||
|
(0.0001, 2, " 100.00 E-06"),
|
||||||
|
// Rounding
|
||||||
|
(0.5, 2, " 500.00 E-03"),
|
||||||
|
(0.5, 1, " 500.0 E-03"),
|
||||||
|
(0.5, 0, " 500. E-03"),
|
||||||
|
(1.5, 2, " 1.50 E+00"),
|
||||||
|
(1.5, 1, " 1.5 E+00"),
|
||||||
|
(1.5, 0, " 2. E+00"),
|
||||||
|
] {
|
||||||
|
assert_eq!(fmt_engineering(f, c), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
src/main.rs
162
src/main.rs
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
mod calc;
|
mod calc;
|
||||||
mod util;
|
mod util;
|
||||||
|
mod format;
|
||||||
|
|
||||||
use calc::constants::{CalculatorDisplayMode, CalculatorState, RegisterState};
|
use calc::constants::{CalculatorDisplayMode, CalculatorState, RegisterState};
|
||||||
use calc::errors::CalculatorResult;
|
use calc::errors::CalculatorResult;
|
||||||
@ -161,13 +162,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
match app.calculator.get_display_mode() {
|
match app.calculator.get_display_mode() {
|
||||||
CalculatorDisplayMode::Default => format!("{:>2}: {}", i, *m),
|
CalculatorDisplayMode::Default => format!("{:>2}: {}", i, *m),
|
||||||
CalculatorDisplayMode::Separated { separator } => {
|
CalculatorDisplayMode::Separated { separator } => {
|
||||||
format!("{:>2}: {}", i, fmt_separated(*m, *separator))
|
format!("{:>2}: {}", i, format::separated(*m, *separator))
|
||||||
}
|
}
|
||||||
CalculatorDisplayMode::Scientific { precision } => {
|
CalculatorDisplayMode::Scientific { precision } => {
|
||||||
format!("{:>2}: {}", i, fmt_scientific(*m, *precision))
|
format!("{:>2}: {}", i, format::scientific(*m, *precision))
|
||||||
}
|
}
|
||||||
CalculatorDisplayMode::Engineering { precision } => {
|
CalculatorDisplayMode::Engineering { precision } => {
|
||||||
format!("{:>2}: {}", i, fmt_engineering(*m, *precision))
|
format!("{:>2}: {}", i, format::engineering(*m, *precision))
|
||||||
}
|
}
|
||||||
CalculatorDisplayMode::Fixed { precision } => {
|
CalculatorDisplayMode::Fixed { precision } => {
|
||||||
format!("{:>2}: {:.precision$}", i, m, precision = precision)
|
format!("{:>2}: {:.precision$}", i, m, precision = precision)
|
||||||
@ -486,158 +487,3 @@ fn draw_clippy_rect<T: std::io::Write>(c: ClippyRectangle, f: &mut Frame<Crosste
|
|||||||
f.render_widget(help_message, area);
|
f.render_widget(help_message, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Based on https://stackoverflow.com/a/65266882
|
|
||||||
fn fmt_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) = if let Some(stripped) = exp.strip_prefix("E-") {
|
|
||||||
('-', stripped)
|
|
||||||
} else {
|
|
||||||
('+', &exp[1..])
|
|
||||||
};
|
|
||||||
|
|
||||||
let sign = if !ret.starts_with('-') { " " } else { "" };
|
|
||||||
format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_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 f64 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 let Some(stripped) = exp_str.strip_prefix("E-") {
|
|
||||||
'-'
|
|
||||||
} 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 + 1)];
|
|
||||||
// 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 + 1)];
|
|
||||||
// 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 fmt_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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_scientific() {
|
|
||||||
for (f, p, s) in vec![
|
|
||||||
// Basic
|
|
||||||
(1.0, 0, " 1 E+00"),
|
|
||||||
(-1.0, 0, "-1 E+00"),
|
|
||||||
(100.0, 0, " 1 E+02"),
|
|
||||||
(0.1, 0, " 1 E-01"),
|
|
||||||
(0.01, 0, " 1 E-02"),
|
|
||||||
(-0.1, 0, "-1 E-01"),
|
|
||||||
// i
|
|
||||||
(1.0, 0, " 1 E+00"),
|
|
||||||
// Precision
|
|
||||||
(-0.123456789, 3, "-1.235 E-01"),
|
|
||||||
(-0.123456789, 2, "-1.23 E-01"),
|
|
||||||
(-0.123456789, 2, "-1.23 E-01"),
|
|
||||||
(-1e99, 2, "-1.00 E+99"),
|
|
||||||
(-1e100, 2, "-1.00 E+100"),
|
|
||||||
// Rounding
|
|
||||||
(0.5, 2, " 5.00 E-01"),
|
|
||||||
(0.5, 1, " 5.0 E-01"),
|
|
||||||
(0.5, 0, " 5 E-01"),
|
|
||||||
(1.5, 2, " 1.50 E+00"),
|
|
||||||
(1.5, 1, " 1.5 E+00"),
|
|
||||||
(1.5, 0, " 2 E+00"),
|
|
||||||
] {
|
|
||||||
assert_eq!(fmt_scientific(f, p), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_separated() {
|
|
||||||
for (f, c, s) in vec![
|
|
||||||
(100.0, ',', "100"),
|
|
||||||
(100.0, ',', "100"),
|
|
||||||
(-100.0, ',', "-100"),
|
|
||||||
(1_000.0, ',', "1,000"),
|
|
||||||
(-1_000.0, ',', "-1,000"),
|
|
||||||
(10_000.0, ',', "10,000"),
|
|
||||||
(-10_000.0, ',', "-10,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.123456789, ',', "1,000,000.123456789"),
|
|
||||||
(-1_000_000.123456789, ',', "-1,000,000.123456789"),
|
|
||||||
(1_000_000.123456789, ' ', "1 000 000.123456789"),
|
|
||||||
(1_000_000.123456789, ' ', "1 000 000.123456789"),
|
|
||||||
] {
|
|
||||||
assert_eq!(fmt_separated(f, c), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_engineering() {
|
|
||||||
for (f, c, 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, 0, " 100. E+00"),
|
|
||||||
(-100.0, 0, "-100. E+00"),
|
|
||||||
(0.1, 2, " 100.00 E-03"),
|
|
||||||
(0.01, 2, " 10.00 E-03"),
|
|
||||||
(0.001, 2, " 1.00 E-03"),
|
|
||||||
(0.0001, 2, " 100.00 E-06"),
|
|
||||||
// Rounding
|
|
||||||
(0.5, 2, " 500.00 E-03"),
|
|
||||||
(0.5, 1, " 500.0 E-03"),
|
|
||||||
(0.5, 0, " 500. E-03"),
|
|
||||||
(1.5, 2, " 1.50 E+00"),
|
|
||||||
(1.5, 1, " 1.5 E+00"),
|
|
||||||
(1.5, 0, " 2. E+00"),
|
|
||||||
] {
|
|
||||||
assert_eq!(fmt_engineering(f, c), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user