Skip to content

Commit

Permalink
Use human friendly numbers by default (unless --precision is used)
Browse files Browse the repository at this point in the history
  • Loading branch information
juan-leon committed Apr 22, 2022
1 parent 43b5ef1 commit f77c695
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 46 deletions.
17 changes: 15 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,24 @@ fn add_intervals(cmd: Command) -> Command {
)
}

fn add_precision(cmd: Command) -> Command {
cmd.arg(
Arg::new("precision")
.long("precision")
.short('p')
.help("Show that number of decimals (if omitted, 'human' units will be used)")
.default_value("-1")
.takes_value(true),
)
}

pub fn get_app() -> Command<'static> {
let mut hist = Command::new("hist")
.version(clap::crate_version!())
.about("Plot an histogram from input values");
hist = add_input(add_regex(add_width(add_min_max(add_intervals(hist)))));
hist = add_input(add_regex(add_width(add_min_max(add_precision(
add_intervals(hist),
)))));

let mut plot = Command::new("plot")
.version(clap::crate_version!())
Expand All @@ -111,7 +124,7 @@ pub fn get_app() -> Command<'static> {
.default_value("40")
.takes_value(true),
);
plot = add_input(add_regex(add_width(add_min_max(plot))));
plot = add_input(add_regex(add_width(add_min_max(add_precision(plot)))));

let mut matches = Command::new("matches")
.version(clap::crate_version!())
Expand Down
158 changes: 158 additions & 0 deletions src/format/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use std::ops::Range;

// Units-based suffixes for human formatting.
const UNITS: &[&str] = &["", " K", " M", " G", " T", " P", " E", " Z", " Y"];

#[derive(Debug)]
pub struct F64Formatter {
/// Decimals digits to be used
decimals: usize,
/// Number of times the value will be divided by 1000
divisor: u8,
/// Suffix (typycally units) to be printed after number
suffix: String,
}

impl F64Formatter {
/// Initializes a new `HumanF64Formatter` with default values.
pub fn new(decimals: usize) -> F64Formatter {
F64Formatter {
decimals,
divisor: 0,
suffix: "".to_owned(),
}
}

/// Initializes a new `HumanF64Formatter` for formatting numbers in the
/// provided range.
pub fn new_with_range(range: Range<f64>) -> F64Formatter {
// Range
let mut decimals = 3;
let mut divisor = 0_u8;
let mut suffix = UNITS[0].to_owned();
let difference = range.end - range.start;
if difference == 0.0 {
return F64Formatter {
decimals,
divisor,
suffix,
};
}
let log = difference.abs().log10() as i64;
if log <= 0 {
decimals = (-log as usize).min(8) + 3;
} else {
decimals = log.rem_euclid(3) as usize;
divisor = ((log - 1) / 3).min(5) as u8;
}
suffix = UNITS[divisor as usize].to_owned();
F64Formatter {
decimals,
divisor,
suffix,
}
}

pub fn format(&self, number: f64) -> String {
format!(
"{:.*}{}",
self.decimals,
number / 1000_usize.pow(self.divisor.into()) as f64,
self.suffix
)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_basic_format() {
assert_eq!(F64Formatter::new(0).format(1000.0), "1000");
assert_eq!(F64Formatter::new(3).format(1000.0), "1000.000");
assert_eq!(F64Formatter::new(1).format(12345.299), "12345.3");
assert_eq!(F64Formatter::new(10).format(3.0), "3.0000000000");
}

#[test]
fn test_human_format_from_zero() {
assert_eq!(F64Formatter::new_with_range(0.0..2.0).format(1.12), "1.120");
assert_eq!(
F64Formatter::new_with_range(0.0..200.0).format(234.12),
"234.12"
);
assert_eq!(
F64Formatter::new_with_range(0.0..1000.0).format(234.1234),
"234"
);
assert_eq!(
F64Formatter::new_with_range(0.0..10000.0).format(234.1234),
"0.2 K"
);
assert_eq!(
F64Formatter::new_with_range(0.0..100000.0).format(234.1234),
"0.23 K"
);
assert_eq!(
F64Formatter::new_with_range(0.0..1000000.0).format(234.1234),
"0 K"
);
assert_eq!(
F64Formatter::new_with_range(0.0..100000000.0).format(1234.1234),
"0.00 M"
);
assert_eq!(
F64Formatter::new_with_range(0.0..1000000.0).format(234000.1234),
"234 K"
);
assert_eq!(
F64Formatter::new_with_range(0.0..100000000.0).format(1234000.1234),
"1.23 M"
);
assert_eq!(
F64Formatter::new_with_range(0.0..100000000.0).format(12340000.1234),
"12.34 M"
);
}

#[test]
fn test_human_format_small_numbers() {
assert_eq!(
F64Formatter::new_with_range(0.0..0.0002).format(0.0000043),
"0.000004"
);
assert_eq!(
F64Formatter::new_with_range(0.0..0.00002).format(0.0000043),
"0.0000043"
);
assert_eq!(
F64Formatter::new_with_range(20000.0..20000.00002).format(20000.0000043),
"20000.0000043"
);
}

#[test]
fn test_human_format_bignum_small_interval() {
assert_eq!(
F64Formatter::new_with_range(100000000.0..100000001.0).format(100000000.12341234),
"100000000.123"
);
}

#[test]
fn test_human_format_negative_start() {
assert_eq!(
F64Formatter::new_with_range(-4.0..2.0).format(1.12),
"1.120"
);
assert_eq!(
F64Formatter::new_with_range(-4.0..-2.0).format(-3.12),
"-3.120"
);
assert_eq!(
F64Formatter::new_with_range(-10000000.0..10.0).format(-3.12),
"-0.0 M"
);
}
}
26 changes: 22 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod app;
mod format;
mod plot;
mod read;
mod stats;
Expand Down Expand Up @@ -108,13 +109,23 @@ fn histogram(matches: &ArgMatches) -> i32 {
if !assert_data(&vec, 1) {
return 1;
}
let stats = stats::Stats::new(&vec);
let precision_arg: i32 = matches.value_of_t("precision").unwrap();
let precision = if precision_arg < 0 {
None
} else {
Some(precision_arg as usize)
};
let stats = stats::Stats::new(&vec, precision);
let width = matches.value_of_t("width").unwrap();
let mut intervals: usize = matches.value_of_t("intervals").unwrap();

intervals = intervals.min(vec.len());
let mut histogram =
plot::Histogram::new(intervals, (stats.max - stats.min) / intervals as f64, stats);
let mut histogram = plot::Histogram::new(
intervals,
(stats.max - stats.min) / intervals as f64,
stats,
precision,
);
histogram.load(&vec);
print!("{:width$}", histogram, width = width);
0
Expand All @@ -130,10 +141,17 @@ fn plot(matches: &ArgMatches) -> i32 {
if !assert_data(&vec, 1) {
return 1;
}
let precision_arg: i32 = matches.value_of_t("precision").unwrap();
let precision = if precision_arg < 0 {
None
} else {
Some(precision_arg as usize)
};
let mut plot = plot::XyPlot::new(
matches.value_of_t("width").unwrap(),
matches.value_of_t("height").unwrap(),
stats::Stats::new(&vec),
stats::Stats::new(&vec, precision),
precision,
);
plot.load(&vec);
print!("{}", plot);
Expand Down
Loading

0 comments on commit f77c695

Please sign in to comment.