use std::fmt;
use std::ops::{Add, AddAssign, Div, Mul, Sub};
use crate::Cycles;
pub type Complexity = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Cost {
pub cycles: Cycles,
pub complexity: Complexity,
saturated: bool,
}
impl Cost {
pub const ZERO: Self = Self { cycles: Cycles::ZERO, complexity: 0, saturated: false };
pub const INFINITE: Self = Self { cycles: Cycles::INFINITE, complexity: 0, saturated: false };
#[must_use]
pub const fn new(cycles: Cycles, complexity: Complexity) -> Self {
Self { cycles, complexity, saturated: false }
}
#[must_use]
pub const fn cycles(cycles: Cycles) -> Self {
Self::new(cycles, 0)
}
#[must_use]
pub const fn is_infinite(self) -> bool {
self.cycles.is_infinite()
}
#[must_use]
pub const fn saturated(self) -> bool {
self.saturated
}
#[must_use]
pub const fn complicated(mut self) -> Self {
self.complexity += 1;
self
}
}
impl Add for Cost {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
cycles: self.cycles + other.cycles,
complexity: self.complexity.saturating_add(other.complexity),
saturated: self.saturated
|| other.saturated
|| self.cycles.adding_saturates(other.cycles),
}
}
}
impl AddAssign for Cost {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl Sub for Cost {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
cycles: self.cycles - other.cycles,
complexity: self.complexity.saturating_sub(other.complexity),
saturated: self.saturated || other.saturated,
}
}
}
impl Mul<i64> for Cost {
type Output = Self;
fn mul(self, by: i64) -> Self {
Self {
cycles: self.cycles * by,
complexity: self.complexity,
saturated: self.saturated || self.cycles.scaling_saturates(by),
}
}
}
impl Div<i64> for Cost {
type Output = Self;
fn div(self, by: i64) -> Self {
Self { cycles: self.cycles / by, complexity: self.complexity, saturated: self.saturated }
}
}
impl Ord for Cost {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.cycles.cmp(&other.cycles).then(self.complexity.cmp(&other.complexity))
}
}
impl PartialOrd for Cost {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Cost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.cycles)?;
if self.complexity != 0 {
write!(f, " (complexity {})", self.complexity)?;
}
if self.saturated {
f.write_str(" (saturated)")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Cost;
use crate::Cycles;
#[test]
fn a_faster_thing_wins_however_complicated_it_is() {
let fast = Cost::new(Cycles::insns(1), 9);
let slow = Cost::new(Cycles::insns(2), 0);
assert!(fast < slow);
}
#[test]
fn equal_times_are_broken_by_the_simpler_one() {
let simple = Cost::new(Cycles::insns(1), 0);
let elaborate = Cost::new(Cycles::insns(1), 3);
assert!(simple < elaborate);
assert_eq!(simple.complicated().complicated().complicated(), elaborate);
}
#[test]
fn infinity_loses_to_everything_and_stays_infinite() {
assert!(Cost::INFINITE > Cost::new(Cycles::insns(1_000), 100));
assert!((Cost::INFINITE + Cost::cycles(Cycles::ONE)).is_infinite());
assert!(!Cost::INFINITE.saturated());
}
#[test]
fn saturating_is_recorded_rather_than_silent() {
let big = Cost::cycles(Cycles::hundredths(i64::MAX - 1));
let sum = big + big;
assert!(sum.is_infinite());
assert!(sum.saturated(), "section 40.13 asks for this to be visible");
assert!((sum + Cost::ZERO).saturated());
assert!((sum - Cost::ZERO).saturated());
assert!((sum * 2).saturated());
assert!((sum / 2).saturated());
}
#[test]
fn an_honest_infinity_is_not_a_saturation() {
let impossible = Cost::INFINITE;
let overflowed = Cost::cycles(Cycles::hundredths(i64::MAX - 1)) * 4;
assert_eq!(impossible.cycles, overflowed.cycles);
assert!(!impossible.saturated());
assert!(overflowed.saturated());
}
#[test]
fn a_repeat_count_scales_the_time_and_not_the_complexity() {
let block = Cost::new(Cycles::insns(3), 2);
let loop_body = block * 1_000;
assert_eq!(loop_body.cycles, Cycles::insns(3_000));
assert_eq!(loop_body.complexity, 2);
}
#[test]
fn printing_says_what_is_there_and_nothing_else() {
assert_eq!(Cost::cycles(Cycles::insns(2)).to_string(), "2");
assert_eq!(Cost::new(Cycles::insns(2), 3).to_string(), "2 (complexity 3)");
assert_eq!(Cost::INFINITE.to_string(), "infinite");
}
}