/// fstop-print-calc calculates exposure times relative to a base time
/// using fractions of f-stops.
-#[derive(Parser, Debug)]
+#[derive(Parser, Debug, Default)]
struct CalcArgs {
/// Starting time to calculate relative to.
time: f64,
}
}
+fn parse_stop_string(base: f64, stop: &str) -> FstopEntry {
+ // parse string manually:
+ let (sign_str, fraction) = stop.split_at(1);
+ let sign: f64 = match sign_str {
+ "+" => { 1. },
+ "-" => { -1. },
+ _ => panic!("Stop missing +/- sign. Format must be +X/Y or -X/Y."),
+ };
+ let slash_offs = fraction.find("/").expect("Stop missing fractional slash. Format must be +X/Y or -X/Y.");
+ let (num, denom) = fraction.split_at(slash_offs);
+ let denom: &str = &denom[1..];
+ let _ = frac_range(denom).expect(&format!("Stop denominator not in range 1-{}. Format must be +X/Y or -X/Y.", MAX_DENOMINATOR));
+ let num: f64 = num.parse::<u32>().expect("Stop numerator not an integer. Format must be +X/Y or -X/Y.") as f64;
+ let denom: f64 = denom.parse::<u32>().expect("Stop numerator not an integer. Format must be +X/Y or -X/Y.") as f64;
+
+ // calculate
+ FstopEntry::new(base, sign * num, denom)
+}
+
fn main() {
let cli: CalcArgs = CalcArgs::parse();
match &cli.stop {
Some(stop) => {
- // parse string manually:
- let (sign_str, fraction) = stop.split_at(1);
- let sign: f64 = match sign_str {
- "+" => { 1. },
- "-" => { -1. },
- _ => panic!("Stop missing +/- sign. Format must be +X/Y or -X/Y."),
- };
- let slash_offs = fraction.find("/").expect("Stop missing fractional slash. Format must be +X/Y or -X/Y.");
- let (num, denom) = fraction.split_at(slash_offs);
- let denom: &str = &denom[1..];
- let _ = frac_range(denom).expect(&format!("Stop denominator not in range 1-{}. Format must be +X/Y or -X/Y.", MAX_DENOMINATOR));
- let num: f64 = num.parse::<u32>().expect("Stop numerator not an integer. Format must be +X/Y or -X/Y.") as f64;
- let denom: f64 = denom.parse::<u32>().expect("Stop numerator not an integer. Format must be +X/Y or -X/Y.") as f64;
-
- // calculate and print result
- let result = FstopEntry::new(cli.time, sign * num, denom);
+ let result = parse_stop_string(cli.time, stop);
println!("{}: {}", result.offset_str(0, false), result.str(cli.precision(), cli.relative))
},
_ => {
},
}
}
+
+#[cfg(test)]
+mod tests {
+ // Note this useful idiom: importing names from outer (for mod tests) scope.
+ use super::*;
+
+ macro_rules! assert_almost {
+ ($x:expr, $y:expr) => {
+ if ($x - $y).abs() > ($x * 0.0005).abs() {
+ panic!("assertion failed: left nearly right\n - left: {}\n - right: {}\n - diff: {} > {}",
+ $x, $y,
+ ($x - $y).abs(),
+ ($x * 0.0005).abs());
+ }
+ }
+ }
+
+ #[test]
+ fn test_stops_16() {
+ let b16_p2_3 = parse_stop_string(16.0, "+2/3");
+ assert_eq!(b16_p2_3.base, 16.0);
+ assert_eq!(b16_p2_3.frac.numerator, 2.0);
+ assert_eq!(b16_p2_3.frac.num(), 2.0);
+ assert_eq!(b16_p2_3.frac.denominator, 3.0);
+ assert_eq!(b16_p2_3.frac.sign, 1.0);
+ assert_almost!(b16_p2_3.abs(), 25.4);
+
+ let b16_n2_3 = parse_stop_string(16.0, "-2/3");
+ assert_eq!(b16_n2_3.base, 16.0);
+ assert_eq!(b16_n2_3.frac.numerator, 2.0);
+ assert_eq!(b16_n2_3.frac.num(), -2.0);
+ assert_eq!(b16_n2_3.frac.denominator, 3.0);
+ assert_eq!(b16_n2_3.frac.sign, -1.0);
+ assert_almost!(b16_n2_3.abs(), 10.08);
+
+ let b16_p1_4 = parse_stop_string(16.0, "+1/4");
+ assert_eq!(b16_p1_4.base, 16.0);
+ assert_eq!(b16_p1_4.frac.numerator, 1.0);
+ assert_eq!(b16_p1_4.frac.num(), 1.0);
+ assert_eq!(b16_p1_4.frac.denominator, 4.0);
+ assert_eq!(b16_p1_4.frac.sign, 1.0);
+ assert_almost!(b16_p1_4.abs(), 19.03);
+
+ let b16_n1_4 = parse_stop_string(16.0, "-1/4");
+ assert_eq!(b16_n1_4.base, 16.0);
+ assert_eq!(b16_n1_4.frac.numerator, 1.0);
+ assert_eq!(b16_n1_4.frac.num(), -1.0);
+ assert_eq!(b16_n1_4.frac.denominator, 4.0);
+ assert_eq!(b16_n1_4.frac.sign, -1.0);
+ assert_almost!(b16_n1_4.abs(), 13.45);
+
+ let b16_p3_3 = parse_stop_string(16.0, "+3/3");
+ assert_eq!(b16_p3_3.base, 16.0);
+ assert_eq!(b16_p3_3.frac.numerator, 3.0);
+ assert_eq!(b16_p3_3.frac.num(), 3.0);
+ assert_eq!(b16_p3_3.frac.denominator, 3.0);
+ assert_eq!(b16_p3_3.frac.sign, 1.0);
+ assert_almost!(b16_p3_3.abs(), 32.0);
+
+ let b16_n3_3 = parse_stop_string(16.0, "-3/3");
+ assert_eq!(b16_n3_3.base, 16.0);
+ assert_eq!(b16_n3_3.frac.numerator, 3.0);
+ assert_eq!(b16_n3_3.frac.num(), -3.0);
+ assert_eq!(b16_n3_3.frac.denominator, 3.0);
+ assert_eq!(b16_n3_3.frac.sign, -1.0);
+ assert_almost!(b16_n3_3.abs(), 8.0);
+
+ let b16_p5_4 = parse_stop_string(16.0, "+5/4");
+ assert_eq!(b16_p5_4.base, 16.0);
+ assert_eq!(b16_p5_4.frac.numerator, 5.0);
+ assert_eq!(b16_p5_4.frac.num(), 5.0);
+ assert_eq!(b16_p5_4.frac.denominator, 4.0);
+ assert_eq!(b16_p5_4.frac.sign, 1.0);
+ assert_almost!(b16_p5_4.abs(), 38.05);
+
+ let b16_n5_4 = parse_stop_string(16.0, "-5/4");
+ assert_eq!(b16_n5_4.base, 16.0);
+ assert_eq!(b16_n5_4.frac.numerator, 5.0);
+ assert_eq!(b16_n5_4.frac.num(), -5.0);
+ assert_eq!(b16_n5_4.frac.denominator, 4.0);
+ assert_eq!(b16_n5_4.frac.sign, -1.0);
+ assert_almost!(b16_n5_4.abs(), 6.73);
+ }
+
+ #[test]
+ fn test_table_16_3_x2() {
+ let mut cli: CalcArgs = Default::default();
+ cli.time = 16.0;
+ cli.stops = Some(2);
+ cli.fraction = vec!(3);
+ let (col_steps, row_steps, table) = fstop_table(&cli);
+
+ assert_eq!(col_steps.len(), 13);
+ let expected = vec!(
+ Fraction::new(-2., 1.),
+ Fraction::new(-5., 3.),
+ Fraction::new(-4., 3.),
+ Fraction::new(-1., 1.),
+ Fraction::new(-2., 3.),
+ Fraction::new(-1., 3.),
+ Fraction::new(0., 1.),
+ Fraction::new(1., 3.),
+ Fraction::new(2., 3.),
+ Fraction::new(1., 1.),
+ Fraction::new(4., 3.),
+ Fraction::new(5., 3.),
+ Fraction::new(2., 1.),
+ );
+ for (idx, step) in col_steps.iter().enumerate() {
+ assert_eq!(step.num(), expected[idx].num());
+ assert_eq!(step.denom(), expected[idx].denom());
+ }
+
+ assert_eq!(row_steps.len(), 1);
+
+ let expected = vec!(
+ 4.0,
+ 5.04,
+ 6.35,
+ 8.0,
+ 10.08,
+ 12.70,
+ 16.0,
+ 20.16,
+ 25.40,
+ 32.0,
+ 40.31,
+ 50.80,
+ 64.0,
+ );
+ for (idx, step) in table.iter().enumerate() {
+ assert_almost!(step.abs(), expected[idx]);
+ assert_almost!(step.rel(), expected[idx] - 16.0);
+ }
+ }
+
+ #[test]
+ fn test_table_16_4_x2() {
+ let mut cli: CalcArgs = Default::default();
+ cli.time = 16.0;
+ cli.stops = Some(2);
+ cli.fraction = vec!(4);
+ let (col_steps, row_steps, table) = fstop_table(&cli);
+
+ assert_eq!(col_steps.len(), 17);
+ let expected = vec!(
+ Fraction::new(-2., 1.),
+ Fraction::new(-7., 4.),
+ Fraction::new(-6., 4.),
+ Fraction::new(-5., 4.),
+ Fraction::new(-1., 1.),
+ Fraction::new(-3., 4.),
+ Fraction::new(-2., 4.),
+ Fraction::new(-1., 4.),
+ Fraction::new(0., 1.),
+ Fraction::new(1., 4.),
+ Fraction::new(2., 4.),
+ Fraction::new(3., 4.),
+ Fraction::new(1., 1.),
+ Fraction::new(5., 4.),
+ Fraction::new(6., 4.),
+ Fraction::new(7., 4.),
+ Fraction::new(2., 1.),
+ );
+ for (idx, step) in col_steps.iter().enumerate() {
+ assert_eq!(step.num(), expected[idx].num());
+ assert_eq!(step.denom(), expected[idx].denom());
+ }
+
+ assert_eq!(row_steps.len(), 1);
+
+ let expected = vec!(
+ 4.0,
+ 4.756,
+ 5.656,
+ 6.73,
+ 8.0,
+ 9.513,
+ 11.314,
+ 13.454,
+ 16.0,
+ 19.027,
+ 22.627,
+ 26.909,
+ 32.0,
+ 38.05,
+ 45.254,
+ 53.817,
+ 64.0,
+ );
+ for (idx, step) in table.iter().enumerate() {
+ assert_almost!(step.abs(), expected[idx]);
+ assert_almost!(step.rel(), expected[idx] - 16.0);
+ }
+ }
+
+ #[test]
+ fn test_table_16_3_4_x2() {
+ let mut cli: CalcArgs = Default::default();
+ cli.time = 16.0;
+ cli.stops = Some(2);
+ cli.fraction = vec!(3, 4);
+ let (col_steps, row_steps, table) = fstop_table(&cli);
+
+ assert_eq!(col_steps.len(), 25);
+ let expected = vec!(
+ Fraction::new(-2., 1.),
+ Fraction::new(-7., 4.),
+ Fraction::new(-5., 3.),
+ Fraction::new(-6., 4.),
+ Fraction::new(-4., 3.),
+ Fraction::new(-5., 4.),
+ Fraction::new(-1., 1.),
+ Fraction::new(-3., 4.),
+ Fraction::new(-2., 3.),
+ Fraction::new(-2., 4.),
+ Fraction::new(-1., 3.),
+ Fraction::new(-1., 4.),
+ Fraction::new(0., 1.),
+ Fraction::new(1., 4.),
+ Fraction::new(1., 3.),
+ Fraction::new(2., 4.),
+ Fraction::new(2., 3.),
+ Fraction::new(3., 4.),
+ Fraction::new(1., 1.),
+ Fraction::new(5., 4.),
+ Fraction::new(4., 3.),
+ Fraction::new(6., 4.),
+ Fraction::new(5., 3.),
+ Fraction::new(7., 4.),
+ Fraction::new(2., 1.),
+ );
+ for (idx, step) in col_steps.iter().enumerate() {
+ assert_eq!(step.num(), expected[idx].num());
+ assert_eq!(step.denom(), expected[idx].denom());
+ }
+
+ assert_eq!(row_steps.len(), 1);
+
+ let expected = vec!(
+ 4.0,
+ 4.756,
+ 5.04,
+ 5.656,
+ 6.35,
+ 6.73,
+ 8.0,
+ 9.513,
+ 10.08,
+ 11.314,
+ 12.70,
+ 13.454,
+ 16.0,
+ 19.027,
+ 20.159,
+ 22.627,
+ 25.40,
+ 26.909,
+ 32.0,
+ 38.05,
+ 40.317,
+ 45.254,
+ 50.8,
+ 53.817,
+ 64.0,
+ );
+ for (idx, step) in table.iter().enumerate() {
+ assert_almost!(step.abs(), expected[idx]);
+ assert_almost!(step.rel(), expected[idx] - 16.0);
+ }
+ }
+}