diff --git a/coverage.sh b/coverage.sh index 4412e87..157a42f 100755 --- a/coverage.sh +++ b/coverage.sh @@ -16,6 +16,7 @@ eval llvm-cov show target/coverage/debug/opentally -instr-profile=target/coverag -ignore-filename-regex="$HOME/." \ -ignore-filename-regex=rustc \ -ignore-filename-regex=numbers/rational_num.rs \ + -ignore-filename-regex=stv/gregory/prettytable_html.rs \ -ignore-filename-regex=stv/wasm.rs \ -ignore-filename-regex=tests \ -format=html --show-instantiations=false --output-dir=target/coverage/html diff --git a/src/numbers/fixed.rs b/src/numbers/fixed.rs index d315177..15ab3e9 100644 --- a/src/numbers/fixed.rs +++ b/src/numbers/fixed.rs @@ -18,7 +18,7 @@ use super::{Assign, Number}; use ibig::{IBig, ops::Abs}; -use num_traits::{Num, One, Zero}; +use num_traits::{Num, One, Signed, Zero}; use std::cmp::{Ord, PartialEq, PartialOrd}; use std::ops; @@ -57,6 +57,9 @@ impl Number for Fixed { fn describe() -> String { format!("--numbers fixed --decimals {}", get_dps()) } fn pow_assign(&mut self, exponent: i32) { + if exponent < 0 { + todo!(); + } self.0 = self.0.pow(exponent as usize) * get_factor() / get_factor().pow(exponent as usize); } @@ -83,13 +86,11 @@ impl Number for Fixed { fn round_mut(&mut self, dps: usize) { // Only do something if truncating if dps < get_dps() { - // TODO: Streamline - let mut factor = Self::from(10); - factor.pow_assign(-(dps as i32)); - factor /= Self::from(2); + let mut factor = IBig::from(10).pow(get_dps() - dps); + factor /= IBig::from(2); - *self = self.clone() - factor; - self.ceil_mut(dps); + self.0 += factor; + self.floor_mut(dps); } } @@ -105,7 +106,12 @@ impl Number for Fixed { Ok(value) => value, Err(_) => panic!("Syntax Error"), } * get_factor() / IBig::from(10).pow(decimal.len()); - return Self(whole + decimal); + + if whole.is_negative() { + return Self(whole - decimal); + } else { + return Self(whole + decimal); + } } // Parse integer @@ -117,6 +123,26 @@ impl Number for Fixed { } } +#[test] +fn rounding() { + Fixed::set_dps(2); + + let mut x = Fixed::parse("555.50"); x.floor_mut(1); assert_eq!(x, Fixed::parse("555.5")); + let mut x = Fixed::parse("555.52"); x.floor_mut(1); assert_eq!(x, Fixed::parse("555.5")); + let mut x = Fixed::parse("555.55"); x.floor_mut(1); assert_eq!(x, Fixed::parse("555.5")); + let mut x = Fixed::parse("555.57"); x.floor_mut(1); assert_eq!(x, Fixed::parse("555.5")); + + let mut x = Fixed::parse("555.50"); x.ceil_mut(1); assert_eq!(x, Fixed::parse("555.5")); + let mut x = Fixed::parse("555.52"); x.ceil_mut(1); assert_eq!(x, Fixed::parse("555.6")); + let mut x = Fixed::parse("555.55"); x.ceil_mut(1); assert_eq!(x, Fixed::parse("555.6")); + let mut x = Fixed::parse("555.57"); x.ceil_mut(1); assert_eq!(x, Fixed::parse("555.6")); + + let mut x = Fixed::parse("555.50"); x.round_mut(1); assert_eq!(x, Fixed::parse("555.5")); + let mut x = Fixed::parse("555.52"); x.round_mut(1); assert_eq!(x, Fixed::parse("555.5")); + let mut x = Fixed::parse("555.55"); x.round_mut(1); assert_eq!(x, Fixed::parse("555.6")); + let mut x = Fixed::parse("555.57"); x.round_mut(1); assert_eq!(x, Fixed::parse("555.6")); +} + impl Num for Fixed { type FromStrRadixErr = ibig::error::ParseError; fn from_str_radix(str: &str, radix: u32) -> Result { @@ -199,9 +225,7 @@ impl ops::Add for Fixed { impl ops::Sub for Fixed { type Output = Self; - fn sub(self, _rhs: Self) -> Self::Output { - todo!() - } + fn sub(self, rhs: Self) -> Self::Output { Self(self.0 - rhs.0) } } impl ops::Mul for Fixed { @@ -221,6 +245,18 @@ impl ops::Rem for Fixed { } } +#[test] +fn arith_owned_owned() { + Fixed::set_dps(2); + let a = Fixed::parse("123.45"); + let b = Fixed::parse("678.90"); + + assert_eq!(a.clone() + b.clone(), Fixed::parse("802.35")); + assert_eq!(a.clone() - b.clone(), Fixed::parse("-555.45")); + assert_eq!(a.clone() * b.clone(), Fixed::parse("83810.20")); // = 83810.205 rounds to 83810.20 + assert_eq!(a.clone() / b.clone(), Fixed::parse("0.18")); +} + impl ops::Add<&Self> for Fixed { type Output = Self; fn add(self, rhs: &Self) -> Self::Output { Self(self.0 + &rhs.0) } @@ -248,6 +284,18 @@ impl ops::Rem<&Self> for Fixed { } } +#[test] +fn arith_owned_ref() { + Fixed::set_dps(2); + let a = Fixed::parse("123.45"); + let b = Fixed::parse("678.90"); + + assert_eq!(a.clone() + &b, Fixed::parse("802.35")); + assert_eq!(a.clone() - &b, Fixed::parse("-555.45")); + assert_eq!(a.clone() * &b, Fixed::parse("83810.20")); + assert_eq!(a.clone() / &b, Fixed::parse("0.18")); +} + impl ops::AddAssign for Fixed { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; } } @@ -276,6 +324,18 @@ impl ops::RemAssign for Fixed { } } +#[test] +fn arithassign_owned() { + Fixed::set_dps(2); + let a = Fixed::parse("123.45"); + let b = Fixed::parse("678.90"); + + let mut x = a.clone(); x += b.clone(); assert_eq!(x, Fixed::parse("802.35")); + let mut x = a.clone(); x -= b.clone(); assert_eq!(x, Fixed::parse("-555.45")); + let mut x = a.clone(); x *= b.clone(); assert_eq!(x, Fixed::parse("83810.20")); + let mut x = a.clone(); x /= b.clone(); assert_eq!(x, Fixed::parse("0.18")); +} + impl ops::AddAssign<&Self> for Fixed { fn add_assign(&mut self, rhs: &Self) { self.0 += &rhs.0; } } @@ -304,6 +364,18 @@ impl ops::RemAssign<&Self> for Fixed { } } +#[test] +fn arithassign_ref() { + Fixed::set_dps(2); + let a = Fixed::parse("123.45"); + let b = Fixed::parse("678.90"); + + let mut x = a.clone(); x += &b; assert_eq!(x, Fixed::parse("802.35")); + let mut x = a.clone(); x -= &b; assert_eq!(x, Fixed::parse("-555.45")); + let mut x = a.clone(); x *= &b; assert_eq!(x, Fixed::parse("83810.20")); + let mut x = a.clone(); x /= &b; assert_eq!(x, Fixed::parse("0.18")); +} + impl ops::Neg for &Fixed { type Output = Fixed; fn neg(self) -> Self::Output { Fixed(-&self.0) } @@ -336,6 +408,18 @@ impl ops::Rem for &Fixed { } } +#[test] +fn arith_ref_ref() { + Fixed::set_dps(2); + let a = Fixed::parse("123.45"); + let b = Fixed::parse("678.90"); + + assert_eq!(&a + &b, Fixed::parse("802.35")); + assert_eq!(&a - &b, Fixed::parse("-555.45")); + assert_eq!(&a * &b, Fixed::parse("83810.20")); + assert_eq!(&a / &b, Fixed::parse("0.18")); +} + /* impl ops::Add<&&Rational> for &Rational { diff --git a/src/numbers/gfixed.rs b/src/numbers/gfixed.rs index a47d9ea..931e416 100644 --- a/src/numbers/gfixed.rs +++ b/src/numbers/gfixed.rs @@ -18,7 +18,7 @@ use super::{Assign, Number}; use ibig::{IBig, ops::Abs}; -use num_traits::{Num, One, Zero}; +use num_traits::{Num, One, Signed, Zero}; use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; use std::ops; @@ -64,6 +64,9 @@ impl Number for GuardedFixed { fn describe() -> String { format!("--numbers gfixed --decimals {}", get_dps()) } fn pow_assign(&mut self, exponent: i32) { + if exponent < 0 { + todo!(); + } self.0 = self.0.pow(exponent as usize) * get_factor() / get_factor().pow(exponent as usize); } @@ -90,13 +93,11 @@ impl Number for GuardedFixed { fn round_mut(&mut self, dps: usize) { // Only do something if truncating if dps < get_dps() * 2 { - // TODO: Streamline - let mut factor = Self::from(10); - factor.pow_assign(-(dps as i32)); - factor /= Self::from(2); + let mut factor = IBig::from(10).pow(get_dps() * 2 - dps); + factor /= IBig::from(2); - *self = self.clone() - factor; - self.ceil_mut(dps); + self.0 += factor; + self.floor_mut(dps); } } @@ -112,7 +113,12 @@ impl Number for GuardedFixed { Ok(value) => value, Err(_) => panic!("Syntax Error"), } * get_factor() / IBig::from(10).pow(decimal.len()); - return Self(whole + decimal); + + if whole.is_negative() { + return Self(whole - decimal); + } else { + return Self(whole + decimal); + } } // Parse integer @@ -124,6 +130,26 @@ impl Number for GuardedFixed { } } +#[test] +fn rounding() { + GuardedFixed::set_dps(2); + + let mut x = GuardedFixed::parse("555.50"); x.floor_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + let mut x = GuardedFixed::parse("555.52"); x.floor_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + let mut x = GuardedFixed::parse("555.55"); x.floor_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + let mut x = GuardedFixed::parse("555.57"); x.floor_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + + let mut x = GuardedFixed::parse("555.50"); x.ceil_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + let mut x = GuardedFixed::parse("555.52"); x.ceil_mut(1); assert_eq!(x, GuardedFixed::parse("555.6")); + let mut x = GuardedFixed::parse("555.55"); x.ceil_mut(1); assert_eq!(x, GuardedFixed::parse("555.6")); + let mut x = GuardedFixed::parse("555.57"); x.ceil_mut(1); assert_eq!(x, GuardedFixed::parse("555.6")); + + let mut x = GuardedFixed::parse("555.50"); x.round_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + let mut x = GuardedFixed::parse("555.52"); x.round_mut(1); assert_eq!(x, GuardedFixed::parse("555.5")); + let mut x = GuardedFixed::parse("555.55"); x.round_mut(1); assert_eq!(x, GuardedFixed::parse("555.6")); + let mut x = GuardedFixed::parse("555.57"); x.round_mut(1); assert_eq!(x, GuardedFixed::parse("555.6")); +} + impl Num for GuardedFixed { type FromStrRadixErr = ibig::error::ParseError; fn from_str_radix(str: &str, radix: u32) -> Result { @@ -231,9 +257,7 @@ impl ops::Add for GuardedFixed { impl ops::Sub for GuardedFixed { type Output = Self; - fn sub(self, _rhs: Self) -> Self::Output { - todo!() - } + fn sub(self, rhs: Self) -> Self::Output { Self(self.0 - rhs.0) } } impl ops::Mul for GuardedFixed { @@ -253,6 +277,18 @@ impl ops::Rem for GuardedFixed { } } +#[test] +fn arith_owned_owned() { + GuardedFixed::set_dps(2); + let a = GuardedFixed::parse("123.45"); + let b = GuardedFixed::parse("678.90"); + + assert_eq!(a.clone() + b.clone(), GuardedFixed::parse("802.35")); + assert_eq!(a.clone() - b.clone(), GuardedFixed::parse("-555.45")); + assert_eq!(a.clone() * b.clone(), GuardedFixed::parse("83810.205")); // Must compare to 3 d.p.s as doesn't meet FACTOR_CMP + assert_eq!(a.clone() / b.clone(), GuardedFixed::parse("0.18")); // Meets FACTOR_CMP so compare only 2 d.p.s +} + impl ops::Add<&Self> for GuardedFixed { type Output = Self; fn add(self, rhs: &Self) -> Self::Output { Self(self.0 + &rhs.0) } @@ -280,6 +316,18 @@ impl ops::Rem<&Self> for GuardedFixed { } } +#[test] +fn arith_owned_ref() { + GuardedFixed::set_dps(2); + let a = GuardedFixed::parse("123.45"); + let b = GuardedFixed::parse("678.90"); + + assert_eq!(a.clone() + &b, GuardedFixed::parse("802.35")); + assert_eq!(a.clone() - &b, GuardedFixed::parse("-555.45")); + assert_eq!(a.clone() * &b, GuardedFixed::parse("83810.205")); + assert_eq!(a.clone() / &b, GuardedFixed::parse("0.18")); +} + impl ops::AddAssign for GuardedFixed { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; } } @@ -308,6 +356,18 @@ impl ops::RemAssign for GuardedFixed { } } +#[test] +fn arithassign_owned() { + GuardedFixed::set_dps(2); + let a = GuardedFixed::parse("123.45"); + let b = GuardedFixed::parse("678.90"); + + let mut x = a.clone(); x += b.clone(); assert_eq!(x, GuardedFixed::parse("802.35")); + let mut x = a.clone(); x -= b.clone(); assert_eq!(x, GuardedFixed::parse("-555.45")); + let mut x = a.clone(); x *= b.clone(); assert_eq!(x, GuardedFixed::parse("83810.205")); + let mut x = a.clone(); x /= b.clone(); assert_eq!(x, GuardedFixed::parse("0.18")); +} + impl ops::AddAssign<&Self> for GuardedFixed { fn add_assign(&mut self, rhs: &Self) { self.0 += &rhs.0; } } @@ -336,6 +396,18 @@ impl ops::RemAssign<&Self> for GuardedFixed { } } +#[test] +fn arithassign_ref() { + GuardedFixed::set_dps(2); + let a = GuardedFixed::parse("123.45"); + let b = GuardedFixed::parse("678.90"); + + let mut x = a.clone(); x += &b; assert_eq!(x, GuardedFixed::parse("802.35")); + let mut x = a.clone(); x -= &b; assert_eq!(x, GuardedFixed::parse("-555.45")); + let mut x = a.clone(); x *= &b; assert_eq!(x, GuardedFixed::parse("83810.205")); + let mut x = a.clone(); x /= &b; assert_eq!(x, GuardedFixed::parse("0.18")); +} + impl ops::Neg for &GuardedFixed { type Output = GuardedFixed; fn neg(self) -> Self::Output { GuardedFixed(-&self.0) } @@ -368,6 +440,18 @@ impl ops::Rem for &GuardedFixed { } } +#[test] +fn arith_ref_ref() { + GuardedFixed::set_dps(2); + let a = GuardedFixed::parse("123.45"); + let b = GuardedFixed::parse("678.90"); + + assert_eq!(&a + &b, GuardedFixed::parse("802.35")); + assert_eq!(&a - &b, GuardedFixed::parse("-555.45")); + assert_eq!(&a * &b, GuardedFixed::parse("83810.205")); + assert_eq!(&a / &b, GuardedFixed::parse("0.18")); +} + /* impl ops::Add<&&Rational> for &Rational { diff --git a/src/numbers/native.rs b/src/numbers/native.rs index f33c764..dee6fa4 100644 --- a/src/numbers/native.rs +++ b/src/numbers/native.rs @@ -54,6 +54,24 @@ impl Number for NativeFloat64 { } } +#[test] +fn rounding() { + let mut x = NativeFloat64::parse("55.550"); x.floor_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + let mut x = NativeFloat64::parse("55.552"); x.floor_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + let mut x = NativeFloat64::parse("55.555"); x.floor_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + let mut x = NativeFloat64::parse("55.557"); x.floor_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + + let mut x = NativeFloat64::parse("55.550"); x.ceil_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + let mut x = NativeFloat64::parse("55.552"); x.ceil_mut(2); assert_eq!(x, NativeFloat64::parse("55.56")); + let mut x = NativeFloat64::parse("55.555"); x.ceil_mut(2); assert_eq!(x, NativeFloat64::parse("55.56")); + let mut x = NativeFloat64::parse("55.557"); x.ceil_mut(2); assert_eq!(x, NativeFloat64::parse("55.56")); + + let mut x = NativeFloat64::parse("55.550"); x.round_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + let mut x = NativeFloat64::parse("55.552"); x.round_mut(2); assert_eq!(x, NativeFloat64::parse("55.55")); + let mut x = NativeFloat64::parse("55.555"); x.round_mut(2); assert_eq!(x, NativeFloat64::parse("55.56")); + let mut x = NativeFloat64::parse("55.557"); x.round_mut(2); assert_eq!(x, NativeFloat64::parse("55.56")); +} + impl Num for NativeFloat64 { type FromStrRadixErr = ParseFloatError; fn from_str_radix(str: &str, radix: u32) -> Result { @@ -76,6 +94,10 @@ impl From for NativeFloat64 { fn from(n: usize) -> Self { Self(n as ImplType) } } +impl From for NativeFloat64 { + fn from(n: f64) -> Self { Self(n as ImplType) } +} + impl One for NativeFloat64 { fn one() -> Self { Self(1.0) } } @@ -102,9 +124,7 @@ impl ops::Add for NativeFloat64 { impl ops::Sub for NativeFloat64 { type Output = NativeFloat64; - fn sub(self, _rhs: Self) -> Self::Output { - todo!() - } + fn sub(self, rhs: Self) -> Self::Output { Self(self.0 - rhs.0) } } impl ops::Mul for NativeFloat64 { @@ -124,6 +144,17 @@ impl ops::Rem for NativeFloat64 { } } +#[test] +fn arith_owned_owned() { + let a = NativeFloat64::parse("123.45"); + let b = NativeFloat64::parse("678.90"); + + assert_eq!(a.clone() + b.clone(), NativeFloat64::from(123.45_f64 + 678.90_f64)); + assert_eq!(a.clone() - b.clone(), NativeFloat64::from(123.45_f64 - 678.90_f64)); + assert_eq!(a.clone() * b.clone(), NativeFloat64::from(123.45_f64 * 678.90_f64)); + assert_eq!(a.clone() / b.clone(), NativeFloat64::from(123.45_f64 / 678.90_f64)); +} + impl ops::Add<&NativeFloat64> for NativeFloat64 { type Output = NativeFloat64; fn add(self, rhs: &NativeFloat64) -> Self::Output { Self(self.0 + &rhs.0) } @@ -151,6 +182,17 @@ impl ops::Rem<&NativeFloat64> for NativeFloat64 { } } +#[test] +fn arith_owned_ref() { + let a = NativeFloat64::parse("123.45"); + let b = NativeFloat64::parse("678.90"); + + assert_eq!(a.clone() + &b, NativeFloat64::from(123.45_f64 + 678.90_f64)); + assert_eq!(a.clone() - &b, NativeFloat64::from(123.45_f64 - 678.90_f64)); + assert_eq!(a.clone() * &b, NativeFloat64::from(123.45_f64 * 678.90_f64)); + assert_eq!(a.clone() / &b, NativeFloat64::from(123.45_f64 / 678.90_f64)); +} + impl ops::AddAssign for NativeFloat64 { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; } } @@ -173,6 +215,17 @@ impl ops::RemAssign for NativeFloat64 { } } +#[test] +fn arithassign_owned() { + let a = NativeFloat64::parse("123.45"); + let b = NativeFloat64::parse("678.90"); + + let mut x = a.clone(); x += b.clone(); assert_eq!(x, NativeFloat64::from(123.45_f64 + 678.90_f64)); + let mut x = a.clone(); x -= b.clone(); assert_eq!(x, NativeFloat64::from(123.45_f64 - 678.90_f64)); + let mut x = a.clone(); x *= b.clone(); assert_eq!(x, NativeFloat64::from(123.45_f64 * 678.90_f64)); + let mut x = a.clone(); x /= b.clone(); assert_eq!(x, NativeFloat64::from(123.45_f64 / 678.90_f64)); +} + impl ops::AddAssign<&NativeFloat64> for NativeFloat64 { fn add_assign(&mut self, rhs: &NativeFloat64) { self.0 += &rhs.0; } } @@ -195,6 +248,17 @@ impl ops::RemAssign<&NativeFloat64> for NativeFloat64 { } } +#[test] +fn arithassign_ref() { + let a = NativeFloat64::parse("123.45"); + let b = NativeFloat64::parse("678.90"); + + let mut x = a.clone(); x += &b; assert_eq!(x, NativeFloat64::from(123.45_f64 + 678.90_f64)); + let mut x = a.clone(); x -= &b; assert_eq!(x, NativeFloat64::from(123.45_f64 - 678.90_f64)); + let mut x = a.clone(); x *= &b; assert_eq!(x, NativeFloat64::from(123.45_f64 * 678.90_f64)); + let mut x = a.clone(); x /= &b; assert_eq!(x, NativeFloat64::from(123.45_f64 / 678.90_f64)); +} + impl ops::Neg for &NativeFloat64 { type Output = NativeFloat64; fn neg(self) -> Self::Output { NativeFloat64(-&self.0) } @@ -227,6 +291,17 @@ impl ops::Rem for &NativeFloat64 { } } +#[test] +fn arith_ref_ref() { + let a = NativeFloat64::parse("123.45"); + let b = NativeFloat64::parse("678.90"); + + assert_eq!(&a + &b, NativeFloat64::from(123.45_f64 + 678.90_f64)); + assert_eq!(&a - &b, NativeFloat64::from(123.45_f64 - 678.90_f64)); + assert_eq!(&a * &b, NativeFloat64::from(123.45_f64 * 678.90_f64)); + assert_eq!(&a / &b, NativeFloat64::from(123.45_f64 / 678.90_f64)); +} + /* impl ops::Add<&&NativeFloat> for &NativeFloat { diff --git a/src/numbers/rational_rug.rs b/src/numbers/rational_rug.rs index bbbfd07..f1cfc14 100644 --- a/src/numbers/rational_rug.rs +++ b/src/numbers/rational_rug.rs @@ -70,8 +70,8 @@ impl Number for Rational { factor.pow_assign(-(dps as i32)); factor /= Self::from(2); - *self = self.clone() - factor; - self.ceil_mut(dps); + *self = self.clone() + factor; + self.floor_mut(dps); } } @@ -87,7 +87,12 @@ impl Number for Rational { Ok(value) => rug::Rational::from(value), Err(_) => panic!("Syntax Error"), } / rug::Rational::from(10).pow(decimal.len() as u32); - return Self(whole + decimal); + + if whole < rug::Rational::new() { + return Self(whole - decimal); + } else { + return Self(whole + decimal); + } } // Parse integer @@ -99,6 +104,24 @@ impl Number for Rational { } } +#[test] +fn rounding() { + let mut x = Rational::parse("55.550"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55")); + let mut x = Rational::parse("55.552"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55")); + let mut x = Rational::parse("55.555"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55")); + let mut x = Rational::parse("55.557"); x.floor_mut(2); assert_eq!(x, Rational::parse("55.55")); + + let mut x = Rational::parse("55.550"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.55")); + let mut x = Rational::parse("55.552"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.56")); + let mut x = Rational::parse("55.555"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.56")); + let mut x = Rational::parse("55.557"); x.ceil_mut(2); assert_eq!(x, Rational::parse("55.56")); + + let mut x = Rational::parse("55.550"); x.round_mut(2); assert_eq!(x, Rational::parse("55.55")); + let mut x = Rational::parse("55.552"); x.round_mut(2); assert_eq!(x, Rational::parse("55.55")); + let mut x = Rational::parse("55.555"); x.round_mut(2); assert_eq!(x, Rational::parse("55.56")); + let mut x = Rational::parse("55.557"); x.round_mut(2); assert_eq!(x, Rational::parse("55.56")); +} + impl Num for Rational { type FromStrRadixErr = ParseRationalError; fn from_str_radix(str: &str, radix: u32) -> Result { @@ -178,9 +201,7 @@ impl ops::Add for Rational { impl ops::Sub for Rational { type Output = Self; - fn sub(self, _rhs: Self) -> Self::Output { - todo!() - } + fn sub(self, rhs: Self) -> Self::Output { Self(self.0 - rhs.0) } } impl ops::Mul for Rational { @@ -200,6 +221,17 @@ impl ops::Rem for Rational { } } +#[test] +fn arith_owned_owned() { + let a = Rational::parse("123.45"); + let b = Rational::parse("678.90"); + + assert_eq!(a.clone() + b.clone(), Rational::parse("802.35")); + assert_eq!(a.clone() - b.clone(), Rational::parse("-555.45")); + assert_eq!(a.clone() * b.clone(), Rational::parse("83810.205")); + assert_eq!((a.clone() / b.clone()) * b, a); +} + impl ops::Add<&Self> for Rational { type Output = Self; fn add(self, rhs: &Self) -> Self::Output { Self(self.0 + &rhs.0) } @@ -227,6 +259,17 @@ impl ops::Rem<&Self> for Rational { } } +#[test] +fn arith_owned_ref() { + let a = Rational::parse("123.45"); + let b = Rational::parse("678.90"); + + assert_eq!(a.clone() + &b, Rational::parse("802.35")); + assert_eq!(a.clone() - &b, Rational::parse("-555.45")); + assert_eq!(a.clone() * &b, Rational::parse("83810.205")); + assert_eq!((a.clone() / &b) * b, a); +} + impl ops::AddAssign for Rational { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; } } @@ -249,6 +292,17 @@ impl ops::RemAssign for Rational { } } +#[test] +fn arithassign_owned() { + let a = Rational::parse("123.45"); + let b = Rational::parse("678.90"); + + let mut x = a.clone(); x += b.clone(); assert_eq!(x, Rational::parse("802.35")); + let mut x = a.clone(); x -= b.clone(); assert_eq!(x, Rational::parse("-555.45")); + let mut x = a.clone(); x *= b.clone(); assert_eq!(x, Rational::parse("83810.205")); + let mut x = a.clone(); x /= b.clone(); x *= b; assert_eq!(x, a); +} + impl ops::AddAssign<&Self> for Rational { fn add_assign(&mut self, rhs: &Self) { self.0 += &rhs.0; } } @@ -271,6 +325,17 @@ impl ops::RemAssign<&Self> for Rational { } } +#[test] +fn arithassign_ref() { + let a = Rational::parse("123.45"); + let b = Rational::parse("678.90"); + + let mut x = a.clone(); x += &b; assert_eq!(x, Rational::parse("802.35")); + let mut x = a.clone(); x -= &b; assert_eq!(x, Rational::parse("-555.45")); + let mut x = a.clone(); x *= &b; assert_eq!(x, Rational::parse("83810.205")); + let mut x = a.clone(); x /= &b; x *= b; assert_eq!(x, a); +} + impl ops::Neg for &Rational { type Output = Rational; fn neg(self) -> Self::Output { Rational(rug::Rational::from(-&self.0)) } @@ -303,6 +368,17 @@ impl ops::Rem for &Rational { } } +#[test] +fn arith_ref_ref() { + let a = Rational::parse("123.45"); + let b = Rational::parse("678.90"); + + assert_eq!(&a + &b, Rational::parse("802.35")); + assert_eq!(&a - &b, Rational::parse("-555.45")); + assert_eq!(&a * &b, Rational::parse("83810.205")); + assert_eq!((&a / &b) * b, a); +} + /* impl ops::Add<&&Rational> for &Rational { diff --git a/tests/data/ers97_csv_output.csv b/tests/data/ers97_csv_output.csv new file mode 100644 index 0000000..688fbf3 --- /dev/null +++ b/tests/data/ers97_csv_output.csv @@ -0,0 +1,24 @@ +"Election for","ERS Model - 1997 Edition" +"Date"," / / " +"Number to be elected",6 +"Valid votes",753 +"Invalid votes",0 +"Quota",107.58 +"OpenTally","VERSION" +"Election rules","--round-surplus-fractions 2 --round-values 2 --round-votes 2 --round-quota 2 --quota droop_exact --quota-criterion geq --quota-mode ers97 --surplus eg --transferable-only --exclusion by_value --bulk-exclude --defer-surpluses" +,,"Stage",2,"Stage",3,"Stage",4,"Stage",5,"Stage",6,"Stage",7,"Stage",8 +,"First","Surplus of",,"Exclusion of",,"Exclusion of",,"Exclusion of",,"Exclusion of",,"Surplus of",,"Exclusion of", +"Candidates","Preferences","Smith",,"Monk",,"Monk",,"Glazier+Wright",,"Glazier+Wright",,"Carpenter",,"Abbot", +"Smith",134,-26.42,107.58,,107.58,,107.58,,107.58,,107.58,,107.58,,107.58,"Elected" +"Duke",105,+1.68,106.68,+2.00,108.68,,108.68,,108.68,,108.68,,108.68,,108.68,"Elected" +"Prince",91,+0.63,91.63,+4.00,95.63,,95.63,+5.00,100.63,+1.68,102.31,+2.00,104.31,,104.31,"Elected" +"Freeman",90,+2.94,92.94,+1.00,93.94,,93.94,+4.00,97.94,+1.68,99.62,+1.00,100.62,+1.00,101.62,"Elected" +"Carpenter",81,+7.14,88.14,,88.14,+0.21,88.35,+34.00,122.35,,122.35,-14.77,107.58,,107.58,"Elected" +"Baron",64,+0.21,64.21,,64.21,,64.21,+3.00,67.21,+1.05,68.26,+2.00,70.26,+12.00,82.26, +"Abbot",59,+0.84,59.84,+5.00,64.84,,64.84,+1.00,65.84,+1.26,67.10,+1.00,68.10,-66.00,2.10, +"Vicar",55,+0.21,55.21,+10.00,65.21,,65.21,+3.00,68.21,+2.10,70.31,+2.00,72.31,+41.00,113.31,"Elected" +"Wright",27,+5.25,32.25,,32.25,,32.25,-27.00,5.25,-5.25,"-",,"-",,"-", +"Glazier",24,+6.51,30.51,,30.51,,30.51,-24.00,6.51,-6.51,"-",,"-",,"-", +"Monk",23,+0.42,23.42,-23.00,0.42,-0.42,"-",,"-",,"-",,"-",,"-", +"Non-transferable",,+0.59,0.59,+1.00,1.59,+0.21,1.80,+1.00,2.80,+3.99,6.79,+6.77,13.56,+12.00,25.56, +"Totals",753,,753.00,,753.00,,753.00,,753.00,,753.00,,753.00,,753.00 diff --git a/tests/tests_impl/cli.rs b/tests/tests_impl/cli.rs index bc178e6..2c15ee6 100644 --- a/tests/tests_impl/cli.rs +++ b/tests/tests_impl/cli.rs @@ -25,6 +25,13 @@ fn cli_ers97old() { .assert().success(); } +#[test] +fn cli_ers97_transfers_detail() { + Command::cargo_bin("opentally").expect("Cargo Error") + .args(&["stv", "tests/data/ers97.blt", "--numbers", "fixed", "--decimals", "5", "--round-surplus-fractions", "2", "--round-values", "2", "--round-votes", "2", "--round-quota", "2", "--quota", "droop_exact", "--quota-criterion", "geq", "--quota-mode", "ers97", "--surplus", "eg", "--transferable-only", "--exclusion", "by_value", "--bulk-exclude", "--defer-surpluses", "--transfers-detail"]) + .assert().success(); +} + #[test] fn cli_meekm_wigm_ties_prompt() { Command::cargo_bin("opentally").expect("Cargo Error") diff --git a/tests/tests_impl/convert.rs b/tests/tests_impl/convert.rs index aec5899..d3ab63b 100644 --- a/tests/tests_impl/convert.rs +++ b/tests/tests_impl/convert.rs @@ -15,43 +15,41 @@ * along with this program. If not, see . */ -use opentally::election::Election; -use opentally::numbers::Rational; -use opentally::parser; -use opentally::writer; +use assert_cmd::Command; use std::fs; #[test] -fn ers97_wd_conversions() { +fn convert_ers97_blt_bin() { let blt_input = fs::read_to_string("tests/data/ers97_wd.blt").expect("IO Error"); - let election: Election = parser::blt::parse_path("tests/data/ers97_wd.blt").expect("Parse Error"); + Command::cargo_bin("opentally").expect("Cargo Error") + .args(&["convert", "tests/data/ers97_wd.blt", "/tmp/opentally.bin"]) + .assert().success(); - // Check BLT - let mut buf: Vec = Vec::new(); - writer::blt::write(election.clone(), &mut buf); - assert_eq!(blt_input, std::str::from_utf8(&buf).expect("UTF-8 Error")); + Command::cargo_bin("opentally").expect("Cargo Error") + .args(&["convert", "/tmp/opentally.bin", "/tmp/opentally.blt"]) + .assert().success(); - // Check BIN - buf.clear(); - writer::bin::write(election.clone(), &mut buf); - let election2: Election = parser::bin::parse_bytes(&buf); - - buf.clear(); - writer::blt::write(election2, &mut buf); - assert_eq!(blt_input, std::str::from_utf8(&buf).expect("UTF-8 Error")); - - // Check CSP - buf.clear(); - writer::csp::write(election.clone(), &mut buf); - let mut election2: Election = parser::csp::parse_reader(&buf[..]); - - election2.seats = election.seats; - election2.name = election.name; - election2.withdrawn_candidates = election.withdrawn_candidates.clone(); - - buf.clear(); - writer::blt::write(election2, &mut buf); - assert_eq!(blt_input, std::str::from_utf8(&buf).expect("UTF-8 Error")); + let blt_roundtrip = fs::read_to_string("/tmp/opentally.blt").expect("IO Error"); + assert_eq!(blt_input, blt_roundtrip); +} + +#[test] +fn convert_ers97_blt_csp() { + // Withdrawn candidates not supported + let blt_input = fs::read_to_string("tests/data/ers97_wd.blt").expect("IO Error") + .replace(r#""ERS Model - 1997 Edition""#, r#""""#) + .replace("-1 -2\n", ""); + + Command::cargo_bin("opentally").expect("Cargo Error") + .args(&["convert", "tests/data/ers97_wd.blt", "/tmp/opentally.csp"]) + .assert().success(); + + Command::cargo_bin("opentally").expect("Cargo Error") + .args(&["convert", "/tmp/opentally.csp", "/tmp/opentally.blt", "--seats", "6"]) + .assert().success(); + + let blt_roundtrip = fs::read_to_string("/tmp/opentally.blt").expect("IO Error"); + assert_eq!(blt_input, blt_roundtrip); } diff --git a/tests/tests_impl/ers.rs b/tests/tests_impl/ers.rs index 18478c2..c52bed2 100644 --- a/tests/tests_impl/ers.rs +++ b/tests/tests_impl/ers.rs @@ -20,6 +20,10 @@ use crate::utils; use opentally::numbers::Rational; use opentally::stv; +use assert_cmd::Command; + +use std::fs; + #[test] fn ers97old_rational() { let stv_opts = stv::STVOptionsBuilder::default() @@ -62,6 +66,20 @@ fn ers97_rational() { utils::read_validate_election::("tests/data/ers97.csv", "tests/data/ers97.blt", stv_opts, None, &["nt", "vre"]); } +#[test] +fn ers97_rational_csv() { + let cmd = Command::cargo_bin("opentally").expect("Cargo Error") + .args(&["stv", "tests/data/ers97.blt", "--round-surplus-fractions", "2", "--round-values", "2", "--round-votes", "2", "--round-quota", "2", "--quota", "droop_exact", "--quota-criterion", "geq", "--quota-mode", "ers97", "--surplus", "eg", "--transferable-only", "--exclusion", "by_value", "--bulk-exclude", "--defer-surpluses", "--output", "csv"]) + .assert().success(); + + let output = std::str::from_utf8(&cmd.get_output().stdout).expect("Unicode Error") + .replace(&format!(r#""{}""#, opentally::VERSION), r#""VERSION""#); + + let expected = fs::read_to_string("tests/data/ers97_csv_output.csv").expect("IO Error"); + + assert_eq!(output, expected); +} + #[test] fn ers76_rational() { let stv_opts = stv::STVOptionsBuilder::default()