use std::fmt;
use std::ops::{Add, AddAssign, Div, Mul, Sub};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Cycles(i64);
impl Cycles {
pub const SCALE: i64 = 100;
pub const ZERO: Self = Self(0);
pub const ONE: Self = Self(Self::SCALE);
pub const INFINITE: Self = Self(i64::MAX);
#[must_use]
pub const fn insns(n: i64) -> Self {
Self(n.saturating_mul(Self::SCALE))
}
#[must_use]
pub const fn hundredths(n: i64) -> Self {
Self(n)
}
#[must_use]
pub const fn is_infinite(self) -> bool {
self.0 == i64::MAX
}
#[must_use]
pub const fn raw(self) -> i64 {
self.0
}
#[must_use]
pub(crate) const fn adding_saturates(self, other: Self) -> bool {
!self.is_infinite() && !other.is_infinite() && self.0.checked_add(other.0).is_none()
}
#[must_use]
pub(crate) const fn scaling_saturates(self, by: i64) -> bool {
!self.is_infinite() && self.0.checked_mul(by).is_none()
}
}
impl Add for Cycles {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
}
impl AddAssign for Cycles {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl Sub for Cycles {
type Output = Self;
fn sub(self, other: Self) -> Self {
if self.is_infinite() {
return Self::INFINITE;
}
Self(self.0.saturating_sub(other.0).max(0))
}
}
impl Mul<i64> for Cycles {
type Output = Self;
fn mul(self, by: i64) -> Self {
if self.is_infinite() {
return Self::INFINITE;
}
Self(self.0.saturating_mul(by))
}
}
impl Div<i64> for Cycles {
type Output = Self;
fn div(self, by: i64) -> Self {
assert!(by != 0, "a cost divided by zero, which is an empty set somewhere upstream");
if self.is_infinite() {
return Self::INFINITE;
}
Self(self.0 / by)
}
}
impl fmt::Display for Cycles {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_infinite() {
return f.write_str("infinite");
}
let whole = self.0 / Self::SCALE;
let part = (self.0 % Self::SCALE).abs();
if part == 0 { write!(f, "{whole}") } else { write!(f, "{whole}.{part:02}") }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Bytes(pub u32);
impl Add for Bytes {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
}
impl AddAssign for Bytes {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl Mul<u32> for Bytes {
type Output = Self;
fn mul(self, by: u32) -> Self {
Self(self.0.saturating_mul(by))
}
}
impl fmt::Display for Bytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} bytes", self.0)
}
}
#[cfg(test)]
mod tests {
use super::{Bytes, Cycles};
#[test]
fn one_operation_is_the_unit() {
assert_eq!(Cycles::ONE, Cycles::insns(1));
assert_eq!(Cycles::insns(3), Cycles::hundredths(300));
assert_eq!(Cycles::ONE.to_string(), "1");
assert_eq!(Cycles::hundredths(133).to_string(), "1.33");
assert_eq!(Cycles::ZERO.to_string(), "0");
}
#[test]
fn infinity_is_above_everything_finite_and_stays_there() {
assert!(Cycles::INFINITE > Cycles::insns(1_000_000));
assert_eq!(Cycles::INFINITE + Cycles::ONE, Cycles::INFINITE);
assert_eq!(Cycles::INFINITE * 3, Cycles::INFINITE);
assert_eq!(Cycles::INFINITE / 3, Cycles::INFINITE);
assert_eq!(Cycles::INFINITE - Cycles::ONE, Cycles::INFINITE);
assert!(Cycles::INFINITE.is_infinite());
assert!(!Cycles::insns(9999).is_infinite());
}
#[test]
fn arithmetic_saturates_rather_than_wrapping() {
let big = Cycles::hundredths(i64::MAX - 1);
assert_eq!(big + big, Cycles::INFINITE);
assert_eq!(big * 2, Cycles::INFINITE);
}
#[test]
fn a_saving_that_came_out_negative_is_no_saving() {
assert_eq!(Cycles::insns(2) - Cycles::insns(5), Cycles::ZERO);
assert_eq!(Cycles::insns(5) - Cycles::insns(2), Cycles::insns(3));
}
#[test]
#[should_panic(expected = "empty set somewhere upstream")]
fn dividing_by_zero_is_a_bug_and_not_an_infinity() {
let _ = Cycles::ONE / 0;
}
#[test]
fn bytes_are_a_different_type_and_do_not_mix_with_cycles() {
let size = Bytes(4) + Bytes(3);
assert_eq!(size, Bytes(7));
assert_eq!((Bytes(2) * 3).to_string(), "6 bytes");
}
}