Start work on formatting

This commit is contained in:
Austen Adler 2021-05-03 18:17:58 -04:00
parent 06c4ab6b55
commit 3b81fc7d1b
4 changed files with 225 additions and 79 deletions

View File

@ -3,9 +3,9 @@ pub mod errors;
pub mod operations; pub mod operations;
use constants::{ use constants::{
CalculatorConstant, CalculatorConstants, CalculatorConstantsIter, CalculatorMacro, CalculatorAngleMode, CalculatorConstant, CalculatorConstants, CalculatorConstantsIter,
CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter, CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorMacrosIter,
CalculatorState, RegisterState, CalculatorRegisters, CalculatorRegistersIter, CalculatorState, RegisterState,
}; };
use errors::{CalculatorError, CalculatorResult}; use errors::{CalculatorError, CalculatorResult};
use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs}; use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs};
@ -33,13 +33,15 @@ pub struct Calculator<'a> {
undo_buf: Vec<CalculatorStateChange>, undo_buf: Vec<CalculatorStateChange>,
redo_buf: Vec<CalculatorStateChange>, redo_buf: Vec<CalculatorStateChange>,
state: CalculatorState, state: CalculatorState,
angle_mode: CalculatorAngleMode,
display_mode: CalculatorDisplayMode,
} }
impl<'a> Default for Calculator<'a> { impl<'a> Default for Calculator<'a> {
fn default() -> Calculator<'a> { fn default() -> Calculator<'a> {
Calculator { Calculator {
l: String::new(), l: String::new(),
stack: vec![1.2, 1.3].into_iter().collect(), stack: vec![1000.0, 1200.32].into_iter().collect(),
state: CalculatorState::Normal, state: CalculatorState::Normal,
undo_buf: vec![], undo_buf: vec![],
redo_buf: vec![], redo_buf: vec![],
@ -125,6 +127,8 @@ impl<'a> Default for Calculator<'a> {
.iter() .iter()
.cloned() .cloned()
.collect(), .collect(),
angle_mode: CalculatorAngleMode::Degrees,
display_mode: CalculatorDisplayMode::Default(None),
} }
} }
} }
@ -175,6 +179,14 @@ impl<'a> Calculator<'a> {
self.state = CalculatorState::WaitingForRegister(RegisterState::Save); self.state = CalculatorState::WaitingForRegister(RegisterState::Save);
Ok(()) Ok(())
} }
'\t' => {
self.state = CalculatorState::WaitingForConstant;
Ok(())
}
'@' => {
self.state = CalculatorState::WaitingForSetting;
Ok(())
}
_ => Err(CalculatorError::NoSuchOperator(c)), _ => Err(CalculatorError::NoSuchOperator(c)),
}, },
CalculatorState::WaitingForConstant => { CalculatorState::WaitingForConstant => {
@ -184,7 +196,9 @@ impl<'a> Calculator<'a> {
.ok_or(CalculatorError::NoSuchConstant(c))? .ok_or(CalculatorError::NoSuchConstant(c))?
.value; .value;
self.push(f) self.push(f)?;
self.state = CalculatorState::Normal;
Ok(())
} }
CalculatorState::WaitingForMacro => { CalculatorState::WaitingForMacro => {
let mac = *self.macros.get(&c).ok_or(CalculatorError::NoSuchMacro(c))?; let mac = *self.macros.get(&c).ok_or(CalculatorError::NoSuchMacro(c))?;
@ -237,6 +251,25 @@ impl<'a> Calculator<'a> {
self.state = CalculatorState::Normal; self.state = CalculatorState::Normal;
Ok(()) Ok(())
} }
CalculatorState::WaitingForSetting => {
match c {
'd' => self.angle_mode = CalculatorAngleMode::Degrees,
'r' => self.angle_mode = CalculatorAngleMode::Radians,
'g' => self.angle_mode = CalculatorAngleMode::Grads,
'_' => self.display_mode = CalculatorDisplayMode::Default(None),
',' => self.display_mode = CalculatorDisplayMode::Default(Some(',')),
' ' => self.display_mode = CalculatorDisplayMode::Default(Some(' ')),
's' => self.display_mode = CalculatorDisplayMode::Scientific(3),
'S' => self.display_mode = CalculatorDisplayMode::Scientific(self.pop_usize()?),
'e' => self.display_mode = CalculatorDisplayMode::Engineering(3),
'E' => {
self.display_mode = CalculatorDisplayMode::Engineering(self.pop_usize()?)
}
_ => return Err(CalculatorError::NoSuchSetting(c)),
};
self.state = CalculatorState::Normal;
Ok(())
}
} }
} }
@ -312,6 +345,12 @@ impl<'a> Calculator<'a> {
pub fn get_stack(&self) -> &VecDeque<f64> { pub fn get_stack(&self) -> &VecDeque<f64> {
&self.stack &self.stack
} }
pub fn get_angle_mode(&self) -> &CalculatorAngleMode {
&self.angle_mode
}
pub fn get_display_mode(&self) -> &CalculatorDisplayMode {
&self.display_mode
}
pub fn flush_l(&mut self) -> CalculatorResult<bool> { pub fn flush_l(&mut self) -> CalculatorResult<bool> {
if self.l.is_empty() { if self.l.is_empty() {
@ -343,7 +382,7 @@ impl<'a> Calculator<'a> {
} }
pub fn pop_usize(&mut self) -> CalculatorResult<usize> { pub fn pop_usize(&mut self) -> CalculatorResult<usize> {
let f = self.checked_get(0)?; let f = self.checked_get(0)?;
let ret = usize::from(f as usize); let ret = f as usize;
self.direct_state_change(CalculatorStateChange { self.direct_state_change(CalculatorStateChange {
pop: OpArgs::Unary(f), pop: OpArgs::Unary(f),
push: OpArgs::None, push: OpArgs::None,
@ -373,12 +412,48 @@ impl<'a> Calculator<'a> {
CalculatorOperation::Dup => self.unary_op(|a| OpArgs::Binary([a, a])), CalculatorOperation::Dup => self.unary_op(|a| OpArgs::Binary([a, a])),
CalculatorOperation::Drop => self.unary_op(|_| OpArgs::None), CalculatorOperation::Drop => self.unary_op(|_| OpArgs::None),
CalculatorOperation::Swap => self.binary_op(|[a, b]| OpArgs::Binary([b, a])), CalculatorOperation::Swap => self.binary_op(|[a, b]| OpArgs::Binary([b, a])),
CalculatorOperation::Sin => self.unary_op(|a| OpArgs::Unary(a.sin())), CalculatorOperation::Sin => self.unary_op(match self.angle_mode {
CalculatorOperation::Cos => self.unary_op(|a| OpArgs::Unary(a.cos())), CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.to_radians().sin()),
CalculatorOperation::Tan => self.unary_op(|a| OpArgs::Unary(a.tan())), CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.sin()),
CalculatorOperation::ASin => self.unary_op(|a| OpArgs::Unary(a.asin())), CalculatorAngleMode::Grads => {
CalculatorOperation::ACos => self.unary_op(|a| OpArgs::Unary(a.acos())), |a: f64| OpArgs::Unary((a * std::f64::consts::PI / 200.0).sin())
CalculatorOperation::ATan => self.unary_op(|a| OpArgs::Unary(a.atan())), }
}),
CalculatorOperation::Cos => self.unary_op(match self.angle_mode {
CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.to_radians().cos()),
CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.cos()),
CalculatorAngleMode::Grads => {
|a: f64| OpArgs::Unary((a * std::f64::consts::PI / 200.0).cos())
}
}),
CalculatorOperation::Tan => self.unary_op(match self.angle_mode {
CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.to_radians().tan()),
CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.tan()),
CalculatorAngleMode::Grads => {
|a: f64| OpArgs::Unary((a * std::f64::consts::PI / 200.0).tan())
}
}),
CalculatorOperation::ASin => self.unary_op(match self.angle_mode {
CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.asin().to_degrees()),
CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.asin()),
CalculatorAngleMode::Grads => {
|a: f64| OpArgs::Unary(a.asin() * std::f64::consts::PI / 200.0)
}
}),
CalculatorOperation::ACos => self.unary_op(match self.angle_mode {
CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.acos().to_degrees()),
CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.acos()),
CalculatorAngleMode::Grads => {
|a: f64| OpArgs::Unary(a.acos() * std::f64::consts::PI / 200.0)
}
}),
CalculatorOperation::ATan => self.unary_op(match self.angle_mode {
CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.atan().to_degrees()),
CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.atan()),
CalculatorAngleMode::Grads => {
|a: f64| OpArgs::Unary(a.atan() * std::f64::consts::PI / 200.0)
}
}),
CalculatorOperation::Sqrt => self.unary_op(|a| OpArgs::Unary(a.sqrt())), CalculatorOperation::Sqrt => self.unary_op(|a| OpArgs::Unary(a.sqrt())),
// CalculatorOperation::Factorial => self.unary_op(|a| OpArgs::Unary(a.())), // CalculatorOperation::Factorial => self.unary_op(|a| OpArgs::Unary(a.())),
CalculatorOperation::Log => self.unary_op(|a| OpArgs::Unary(a.log10())), CalculatorOperation::Log => self.unary_op(|a| OpArgs::Unary(a.log10())),

View File

@ -11,6 +11,7 @@ pub enum CalculatorState {
WaitingForConstant, WaitingForConstant,
WaitingForMacro, WaitingForMacro,
WaitingForRegister(RegisterState), WaitingForRegister(RegisterState),
WaitingForSetting,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -33,3 +34,15 @@ pub type CalculatorMacrosIter<'a> = Iter<'a, char, CalculatorMacro<'a>>;
pub type CalculatorRegisters = HashMap<char, f64>; pub type CalculatorRegisters = HashMap<char, f64>;
pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>; pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>;
pub enum CalculatorAngleMode {
Degrees,
Radians,
Grads,
}
pub enum CalculatorDisplayMode {
Default(Option<char>),
Scientific(usize),
Engineering(usize),
}

View File

@ -11,6 +11,7 @@ pub enum CalculatorError {
NoSuchRegister(char), NoSuchRegister(char),
NoSuchMacro(char), NoSuchMacro(char),
NoSuchOperator(char), NoSuchOperator(char),
NoSuchSetting(char),
RecursiveMacro(char), RecursiveMacro(char),
ParseError, ParseError,
} }
@ -28,6 +29,7 @@ impl fmt::Display for CalculatorError {
CalculatorError::NoSuchConstant(c) => write!(f, "No such constant '{}'", c), CalculatorError::NoSuchConstant(c) => write!(f, "No such constant '{}'", c),
CalculatorError::NoSuchRegister(c) => write!(f, "No such register '{}'", c), CalculatorError::NoSuchRegister(c) => write!(f, "No such register '{}'", c),
CalculatorError::NoSuchMacro(c) => write!(f, "No such macro '{}'", c), CalculatorError::NoSuchMacro(c) => write!(f, "No such macro '{}'", c),
CalculatorError::NoSuchSetting(c) => write!(f, "No such setting '{}'", c),
CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c), CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c),
CalculatorError::ParseError => write!(f, "Parse error"), CalculatorError::ParseError => write!(f, "Parse error"),
} }

View File

@ -4,7 +4,7 @@
mod calc; mod calc;
mod util; mod util;
use calc::constants::{CalculatorState, RegisterState}; use calc::constants::{CalculatorAngleMode, CalculatorDisplayMode, CalculatorState, RegisterState};
use calc::errors::CalculatorResult; use calc::errors::CalculatorResult;
use calc::Calculator; use calc::Calculator;
use std::cmp; use std::cmp;
@ -27,18 +27,7 @@ struct Dimensions {
height: u16, height: u16,
} }
enum DisplayMode {
Default(Option<char>),
Scientific(usize),
Engineering,
}
struct AppSettings {
display_mode: DisplayMode,
}
enum AppState { enum AppState {
AppSettings,
Calculator, Calculator,
Help, Help,
} }
@ -48,7 +37,6 @@ struct App<'a> {
error_msg: Option<String>, error_msg: Option<String>,
state: AppState, state: AppState,
current_macro: Option<char>, current_macro: Option<char>,
app_settings: AppSettings,
} }
impl<'a> Default for App<'a> { impl<'a> Default for App<'a> {
@ -58,9 +46,6 @@ impl<'a> Default for App<'a> {
error_msg: None, error_msg: None,
state: AppState::Calculator, state: AppState::Calculator,
current_macro: None, current_macro: None,
app_settings: AppSettings {
display_mode: DisplayMode::Default(None),
},
} }
} }
} }
@ -97,11 +82,17 @@ fn main() -> Result<(), Box<dyn Error>> {
], ],
(None, AppState::Calculator) => { (None, AppState::Calculator) => {
// TODO: There has to be a better way than making strings each time // TODO: There has to be a better way than making strings each time
let display_mode_str = match &app.app_settings.display_mode { let display_mode_str = match app.calculator.get_display_mode() {
DisplayMode::Default(None) => String::from("[d]"), CalculatorDisplayMode::Default(None) => String::from("DEF"),
DisplayMode::Default(Some(c)) => format!("[d({})]", c), CalculatorDisplayMode::Default(Some(c)) => format!("DEF({})", c),
DisplayMode::Scientific(p) => format!("[s({})]", p), CalculatorDisplayMode::Scientific(p) => format!("SCI({})", p),
DisplayMode::Engineering => String::from("[e]"), CalculatorDisplayMode::Engineering(p) => format!("ENG({})", p),
};
let angle_mode_str = match app.calculator.get_angle_mode() {
CalculatorAngleMode::Degrees => String::from("DEG"),
CalculatorAngleMode::Radians => String::from("RAD"),
CalculatorAngleMode::Grads => String::from("GRD"),
}; };
vec![ vec![
@ -109,8 +100,10 @@ fn main() -> Result<(), Box<dyn Error>> {
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit, "), Span::raw(" to exit, "),
Span::styled("h", Style::default().add_modifier(Modifier::BOLD)), Span::styled("h", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" for help - "), Span::raw(format!(
Span::raw(display_mode_str), " for help - [{}] [{}]",
display_mode_str, angle_mode_str
)),
] ]
} }
(None, _) => vec![ (None, _) => vec![
@ -133,11 +126,13 @@ fn main() -> Result<(), Box<dyn Error>> {
.rev() .rev()
.map(|(i, m)| { .map(|(i, m)| {
let content = vec![Spans::from(Span::raw( let content = vec![Spans::from(Span::raw(
match &app.app_settings.display_mode { match app.calculator.get_display_mode() {
DisplayMode::Default(None) => format!("{:>2}: {}", i, *m), CalculatorDisplayMode::Default(None) => format!("{:>2}: {}", i, *m),
DisplayMode::Default(Some(c)) => fmt_separated(i, *m, *c), CalculatorDisplayMode::Default(Some(c)) => fmt_separated(i, *m, *c),
DisplayMode::Scientific(precision) => fmt_scientific(i, *m, *precision), CalculatorDisplayMode::Scientific(precision) => {
DisplayMode::Engineering => { fmt_scientific(i, *m, *precision)
}
CalculatorDisplayMode::Engineering(_precision) => {
format!("{:>2}: {}", i, m) format!("{:>2}: {}", i, m)
} }
}, },
@ -169,22 +164,6 @@ fn main() -> Result<(), Box<dyn Error>> {
); );
match (&app.state, app.calculator.get_state()) { match (&app.state, app.calculator.get_state()) {
(AppState::AppSettings, _) => {
draw_clippy_rect(
ClippyRectangle {
title: "App Settings",
msg: "\
d => Default\n\
, => Default (comma separated)\n\
s => Scientific\n\
S => Scientific (stack precision)\n\
e => Engineering\n\
E => Engineering (stack precision)\n\
",
},
f,
);
}
(AppState::Help, _) => { (AppState::Help, _) => {
draw_clippy_rect( draw_clippy_rect(
ClippyRectangle { ClippyRectangle {
@ -258,6 +237,24 @@ fn main() -> Result<(), Box<dyn Error>> {
f, f,
); );
} }
(AppState::Calculator, CalculatorState::WaitingForSetting) => {
draw_clippy_rect(
ClippyRectangle {
title: "Help",
msg: "\
d => Degrees\n\
r => Radians\n\
d => Default\n\
, => Default (comma separated)\n\
s => Scientific\n\
S => Scientific (stack precision)\n\
e => Engineering\n\
E => Engineering (stack precision)\
",
},
f,
);
}
_ => {} _ => {}
} }
})?; })?;
@ -299,9 +296,6 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
Key::Char('h') => { Key::Char('h') => {
app.state = AppState::Help; app.state = AppState::Help;
} }
Key::Ctrl('s') => {
app.state = AppState::AppSettings;
}
Key::Char('\n') | Key::Char(' ') => { Key::Char('\n') | Key::Char(' ') => {
app.calculator.take_input(' ')?; app.calculator.take_input(' ')?;
} }
@ -319,20 +313,6 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
} }
_ => {} _ => {}
}, },
(AppState::AppSettings, _) => match key {
Key::Esc | Key::Char('q') => {
app.state = AppState::Calculator;
app.calculator.cancel()?;
}
Key::Char('d') => app.app_settings.display_mode = DisplayMode::Default(None),
Key::Char(',') => app.app_settings.display_mode = DisplayMode::Default(Some(',')),
Key::Char('s') => app.app_settings.display_mode = DisplayMode::Scientific(3),
Key::Char('S') => {
app.app_settings.display_mode = DisplayMode::Scientific(app.calculator.pop_usize()?)
}
Key::Char('e') => app.app_settings.display_mode = DisplayMode::Engineering,
_ => {}
},
(AppState::Help, _) => match key { (AppState::Help, _) => match key {
Key::Esc | Key::Char('q') => { Key::Esc | Key::Char('q') => {
app.state = AppState::Calculator; app.state = AppState::Calculator;
@ -341,6 +321,7 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
_ => {} _ => {}
}, },
(AppState::Calculator, CalculatorState::WaitingForConstant) (AppState::Calculator, CalculatorState::WaitingForConstant)
| (AppState::Calculator, CalculatorState::WaitingForSetting)
| (AppState::Calculator, CalculatorState::WaitingForRegister(_)) | (AppState::Calculator, CalculatorState::WaitingForRegister(_))
| (AppState::Calculator, CalculatorState::WaitingForMacro) => match key { | (AppState::Calculator, CalculatorState::WaitingForMacro) => match key {
Key::Esc => { Key::Esc => {
@ -407,16 +388,16 @@ fn draw_clippy_rect<T: std::io::Write>(c: ClippyRectangle, f: &mut Frame<Termion
// Based on https://stackoverflow.com/a/65266882 // Based on https://stackoverflow.com/a/65266882
fn fmt_scientific(i: usize, f: f64, precision: usize) -> String { fn fmt_scientific(i: usize, f: f64, precision: usize) -> String {
let mut ret = format!("{:.precision$e}", f, precision = precision); let mut ret = format!("{:.precision$E}", f, precision = precision);
let exp = ret.split_off(ret.find('e').unwrap_or(0)); let exp = ret.split_off(ret.find('E').unwrap_or(0));
let (pow_sign, exp) = if exp.starts_with("e-") { let (pow_sign, exp) = if exp.starts_with("E-") {
('-', &exp[2..]) ('-', &exp[2..])
} else { } else {
('+', &exp[1..]) ('+', &exp[1..])
}; };
let sign = if !ret.starts_with('-') { " " } else { "" }; let sign = if !ret.starts_with('-') { " " } else { "" };
format!( format!(
"{:>2}: {}{}e{}{:0>pad$}", "{:>2}: {}{}E{}{:0>pad$}",
i, i,
sign, sign,
ret, ret,
@ -426,12 +407,87 @@ fn fmt_scientific(i: usize, f: f64, precision: usize) -> String {
) )
} }
// // 100 E+3
// fn fmt_engineering(i: usize, f: f64, precision: usize) -> String {
// let mut ret = format!(" {:.precision$e}", f, precision = precision + 2);
// // The length of ret will always be at least 5 -- 2 leading spaces, integer portion, and precision + 2
// let exp = ret.split_off(ret.find('E').unwrap_or(0));
// let left = ret[()..()]
// let first_three = match exp {
// }
// let (pow_sign, exp) = if exp.starts_with("E-") {
// ('-', &exp[2..])
// } else {
// ('+', &exp[1..])
// };
// let sign = if !ret.starts_with('-') { " " } else { "" };
// format!(
// "{:>2}: {}{}e{}{:0>pad$}",
// i,
// sign,
// ret,
// pow_sign,
// exp,
// pad = 2
// )
// }
fn fmt_separated(i: usize, f: f64, sep: char) -> String { fn fmt_separated(i: usize, f: f64, sep: char) -> String {
let mut ret = f.to_string(); let mut ret = f.to_string();
let start = if ret.starts_with('-') { 1 } else { 0 }; let start = if ret.starts_with('-') { 1 } else { 0 };
let end = ret.find('.').unwrap_or(ret.len()); let end = ret.find('.').unwrap_or(ret.len());
for i in 0..((end - start).div_euclid(3)) { for i in 0..((end - start - 1).div_euclid(3)) {
ret.insert(end - start - (i + 1) * 3, sep); ret.insert(end - (i + 1) * 3, sep);
} }
format!("{:>2}: {}", i, ret) format!("{:>2}: {}", i, ret)
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fmt_scientific() {
for (i, f, p, s) in vec![
// Basic
(0, 1.0, 0, " 0: 1E+00"),
(0, -1.0, 0, " 0: -1E+00"),
(0, 100.0, 0, " 0: 1E+02"),
(0, 0.1, 0, " 0: 1E-01"),
(0, 0.01, 0, " 0: 1E-02"),
(0, -0.1, 0, " 0: -1E-01"),
// i
(22, 1.0, 0, "22: 1E+00"),
// Precision
(0, -0.123456789, 3, " 0: -1.235E-01"),
(0, -0.123456789, 2, " 0: -1.23E-01"),
(0, -0.123456789, 2, " 0: -1.23E-01"),
(0, -1e99, 2, " 0: -1.00E+99"),
(0, -1e100, 2, " 0: -1.00E+100"),
] {
assert_eq!(fmt_scientific(i, f, p), s);
}
}
#[test]
fn test_fmt_separated() {
for (i, f, c, s) in vec![
(10, 100.0, ',', "10: 100"),
(0, 100.0, ',', " 0: 100"),
(0, -100.0, ',', " 0: -100"),
(0, 1_000.0, ',', " 0: 1,000"),
(0, -1_000.0, ',', " 0: -1,000"),
(0, 10_000.0, ',', " 0: 10,000"),
(0, -10_000.0, ',', " 0: -10,000"),
(0, 100_000.0, ',', " 0: 100,000"),
(0, -100_000.0, ',', " 0: -100,000"),
(0, 1_000_000.0, ',', " 0: 1,000,000"),
(0, -1_000_000.0, ',', " 0: -1,000,000"),
(0, 1_000_000.123456789, ',', " 0: 1,000,000.123456789"),
(0, -1_000_000.123456789, ',', " 0: -1,000,000.123456789"),
(0, 1_000_000.123456789, ' ', " 0: 1 000 000.123456789"),
(0, 1_000_000.123456789, ' ', " 0: 1 000 000.123456789"),
] {
assert_eq!(fmt_separated(i, f, c), s);
}
}
}