#![no_std]
use core::cmp::Ordering;
use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
pub trait SerialPrimitive: Copy + Ord {
const HALF: Self;
fn wrapping_add(self, rhs: Self) -> Self;
fn wrapping_sub(self, rhs: Self) -> Self;
}
macro_rules! impl_serial_primitive {
($($t:ty),+ $(,)?) => {$(
impl SerialPrimitive for $t {
const HALF: Self = 1 << (<$t>::BITS - 1);
#[inline]
fn wrapping_add(self, rhs: Self) -> Self { <$t>::wrapping_add(self, rhs) }
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self { <$t>::wrapping_sub(self, rhs) }
}
impl PartialEq<Serial<$t>> for $t {
#[inline]
fn eq(&self, other: &Serial<$t>) -> bool {
*self == other.0
}
}
impl PartialOrd<Serial<$t>> for $t {
#[inline]
fn partial_cmp(&self, other: &Serial<$t>) -> Option<Ordering> {
Serial(*self).partial_cmp(other)
}
}
)+};
}
impl_serial_primitive!(u8, u16, u32, u64, u128, usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub struct Serial<T>(T);
impl<T: SerialPrimitive> Serial<T> {
#[inline]
pub const fn new(value: T) -> Self {
Serial(value)
}
#[inline]
pub fn value(self) -> T {
self.0
}
#[inline]
pub fn distance_to(self, other: Self) -> T {
other.0.wrapping_sub(self.0)
}
#[inline]
pub fn precedes(self, other: Self) -> bool {
matches!(self.partial_cmp(&other), Some(Ordering::Less))
}
}
impl<T: SerialPrimitive + fmt::Display> fmt::Display for Serial<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: SerialPrimitive> PartialEq<T> for Serial<T> {
#[inline]
fn eq(&self, other: &T) -> bool {
self.0 == *other
}
}
impl<T: SerialPrimitive> PartialOrd<T> for Serial<T> {
#[inline]
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
self.partial_cmp(&Serial(*other))
}
}
impl<T: SerialPrimitive> PartialOrd for Serial<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.0 == other.0 {
return Some(Ordering::Equal);
}
let d = other.0.wrapping_sub(self.0);
if d == T::HALF {
None
} else if d < T::HALF {
Some(Ordering::Less)
} else {
Some(Ordering::Greater)
}
}
}
impl<T: SerialPrimitive> Add<T> for Serial<T> {
type Output = Serial<T>;
#[inline]
fn add(self, rhs: T) -> Serial<T> {
debug_assert!(rhs < T::HALF, "addend must be < HALF per RFC 1982");
Serial(self.0.wrapping_add(rhs))
}
}
impl<T: SerialPrimitive> Sub<T> for Serial<T> {
type Output = Serial<T>;
#[inline]
fn sub(self, rhs: T) -> Serial<T> {
Serial(self.0.wrapping_sub(rhs))
}
}
impl<T: SerialPrimitive> AddAssign<T> for Serial<T> {
#[inline]
fn add_assign(&mut self, rhs: T) {
debug_assert!(rhs < T::HALF, "addend must be < HALF per RFC 1982");
self.0 = self.0.wrapping_add(rhs);
}
}
impl<T: SerialPrimitive> SubAssign<T> for Serial<T> {
#[inline]
fn sub_assign(&mut self, rhs: T) {
self.0 = self.0.wrapping_sub(rhs);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_wraps_around() {
let a = Serial::<u8>::new(250);
assert_eq!((a + 10).value(), 4);
assert_eq!((a + 5).value(), 255);
assert_eq!((a + 6).value(), 0);
}
#[test]
fn sub_wraps_around() {
let a = Serial::<u8>::new(4);
assert_eq!((a - 10).value(), 250);
assert_eq!((a - 4).value(), 0);
assert_eq!((a - 5).value(), 255);
}
#[test]
fn add_assign_sub_assign() {
let mut a = Serial::<u32>::new(u32::MAX);
a += 1;
assert_eq!(a.value(), 0);
a -= 1;
assert_eq!(a.value(), u32::MAX);
}
#[test]
fn ordering_basic() {
let a = Serial::<u32>::new(1);
let b = Serial::<u32>::new(2);
assert!(a < b);
assert!(b > a);
assert_eq!(a, Serial::<u32>::new(1));
}
#[test]
fn ordering_wraps_around() {
let max = Serial::<u8>::new(u8::MAX);
let zero = Serial::<u8>::new(0);
assert!(max < zero);
assert!(zero > max);
let a = Serial::<u8>::new(250);
let b = Serial::<u8>::new(4);
assert!(a < b);
}
#[test]
fn ordering_undefined_at_half() {
let a = Serial::<u8>::new(0);
let b = Serial::<u8>::new(128);
assert_eq!(a.partial_cmp(&b), None);
assert_eq!(b.partial_cmp(&a), None);
assert!(!(a < b));
assert!(!(a > b));
assert!(!a.precedes(b));
}
#[test]
fn distance_to_is_forward_and_nonnegative() {
let a = Serial::<u8>::new(250);
let b = Serial::<u8>::new(4);
assert_eq!(a.distance_to(b), 10);
assert_eq!(b.distance_to(a), 246);
}
#[test]
fn eq_with_raw_value_both_directions() {
let a = Serial::<u32>::new(42);
assert!(a == 42);
assert!(a != 7);
assert!(42u32 == a);
assert!(7u32 != a);
assert!(Serial::<u8>::new(255) == 255u8);
assert!(0u16 != Serial::<u16>::new(1));
}
#[test]
fn ord_with_raw_value_both_directions() {
let a = Serial::<u8>::new(250);
assert!(a < 4u8);
assert!(a <= 4u8);
assert!(a > 200u8);
assert!(4u8 > a);
assert!(200u8 < a);
assert!(Serial::<u8>::new(u8::MAX) < 0u8);
assert!(0u8 > Serial::<u8>::new(u8::MAX));
}
#[test]
fn ord_with_raw_value_undefined_at_half() {
let a = Serial::<u8>::new(0);
assert_eq!(a.partial_cmp(&128u8), None);
assert_eq!(128u8.partial_cmp(&a), None);
assert!(!(a < 128u8));
assert!(!(a > 128u8));
}
#[test]
fn works_across_widths() {
assert!(Serial::<u16>::new(u16::MAX) < Serial::<u16>::new(0));
assert!(Serial::<u64>::new(u64::MAX) < Serial::<u64>::new(0));
assert_eq!((Serial::<u128>::new(u128::MAX) + 1).value(), 0);
}
}