use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Mul, Sub};
use num_traits::Zero;
use crate::ordered_pair::{AbsDistance, AddWidth, Unsigned};
use super::error::BinaryError;
use super::ubinary::UBinary;
use super::xbinary::XBinary;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UXBinary {
Finite(UBinary),
Inf,
}
impl UXBinary {
pub fn zero() -> Self {
Self::Finite(UBinary::zero())
}
pub fn is_zero(&self) -> bool {
matches!(self, Self::Finite(value) if value.mantissa().is_zero())
}
pub fn try_from_xbinary(xbinary: &XBinary) -> Result<Self, BinaryError> {
match xbinary {
XBinary::NegInf => Err(BinaryError::NegativeMantissa),
XBinary::PosInf => Ok(Self::Inf),
XBinary::Finite(binary) => {
use num_traits::Signed;
if binary.mantissa().is_negative() {
return Err(BinaryError::NegativeMantissa);
}
Ok(Self::Finite(UBinary::try_from_binary(binary)?))
}
}
}
pub fn add(&self, other: &Self) -> Self {
use UXBinary::{Finite, Inf};
match (self, other) {
(Inf, _) | (_, Inf) => Inf,
(Finite(lhs), Finite(rhs)) => Finite(lhs.add(rhs)),
}
}
pub fn mul(&self, other: &Self) -> Self {
use UXBinary::{Finite, Inf};
if self.is_zero() || other.is_zero() {
return Finite(UBinary::zero());
}
match (self, other) {
(Finite(lhs), Finite(rhs)) => Finite(lhs.mul(rhs)),
(Inf, _) | (_, Inf) => Inf,
}
}
pub fn sub_saturating(&self, other: &Self) -> Self {
use UXBinary::{Finite, Inf};
match (self, other) {
(Inf, Finite(_)) => Inf,
(Inf, Inf) => Finite(UBinary::zero()),
(Finite(_), Inf) => Finite(UBinary::zero()),
(Finite(lhs), Finite(rhs)) => Finite(lhs.sub_saturating(rhs)),
}
}
}
impl Ord for UXBinary {
fn cmp(&self, other: &Self) -> Ordering {
use UXBinary::{Finite, Inf};
match (self, other) {
(Inf, Inf) => Ordering::Equal,
(Inf, _) => Ordering::Greater,
(_, Inf) => Ordering::Less,
(Finite(lhs), Finite(rhs)) => lhs.cmp(rhs),
}
}
}
impl PartialOrd for UXBinary {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Zero for UXBinary {
fn zero() -> Self {
UXBinary::zero()
}
fn is_zero(&self) -> bool {
self.is_zero()
}
}
impl Add for UXBinary {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
UXBinary::add(&self, &rhs)
}
}
impl Sub for UXBinary {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
UXBinary::sub_saturating(&self, &rhs)
}
}
impl Mul for UXBinary {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
UXBinary::mul(&self, &rhs)
}
}
impl Unsigned for UXBinary {}
impl From<UXBinary> for XBinary {
fn from(uxbinary: UXBinary) -> Self {
match uxbinary {
UXBinary::Inf => XBinary::PosInf,
UXBinary::Finite(ubinary) => XBinary::Finite(ubinary.to_binary()),
}
}
}
impl AbsDistance<XBinary, UXBinary> for XBinary {
fn abs_distance(self, other: Self) -> UXBinary {
use XBinary::{Finite, NegInf, PosInf};
match (self, other) {
(NegInf, PosInf) | (PosInf, NegInf) => UXBinary::Inf,
(NegInf, NegInf) | (PosInf, PosInf) => UXBinary::zero(),
(NegInf, Finite(_)) | (Finite(_), PosInf) => UXBinary::Inf,
(PosInf, Finite(_)) | (Finite(_), NegInf) => UXBinary::Inf,
(Finite(l), Finite(u)) => UXBinary::Finite(u.sub(l).magnitude()),
}
}
}
impl AddWidth<XBinary, UXBinary> for XBinary {
fn add_width(self, rhs: UXBinary) -> Self {
self + XBinary::from(rhs)
}
}
impl fmt::Display for UXBinary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UXBinary::Finite(ubinary) => write!(f, "{}", ubinary),
UXBinary::Inf => write!(f, "+∞"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{bin, ubin, xbin};
#[test]
fn uxbinary_zero_is_zero() {
assert!(UXBinary::zero().is_zero());
assert!(!UXBinary::Inf.is_zero());
}
#[test]
fn uxbinary_ordering_works() {
let zero = UXBinary::zero();
let one = UXBinary::Finite(ubin(1, 0));
let inf = UXBinary::Inf;
assert!(zero < one);
assert!(one < inf);
assert!(zero < inf);
assert_eq!(inf, UXBinary::Inf);
}
#[test]
fn uxbinary_add_works() {
let one = UXBinary::Finite(ubin(1, 0));
let two = UXBinary::Finite(ubin(1, 1));
let sum = one.clone() + two.clone();
assert_eq!(sum, UXBinary::Finite(ubin(3, 0)));
let inf = UXBinary::Inf;
assert_eq!(one.clone() + inf.clone(), UXBinary::Inf);
assert_eq!(inf + one, UXBinary::Inf);
}
#[test]
fn uxbinary_sub_saturating_works() {
let two = UXBinary::Finite(ubin(1, 1));
let one = UXBinary::Finite(ubin(1, 0));
let diff = two.sub_saturating(&one);
assert_eq!(diff, UXBinary::Finite(ubin(1, 0)));
let saturated = one.sub_saturating(&two);
assert_eq!(saturated, UXBinary::zero());
let inf = UXBinary::Inf;
assert_eq!(inf.sub_saturating(&one), UXBinary::Inf);
assert_eq!(inf.sub_saturating(&inf), UXBinary::zero());
assert_eq!(one.sub_saturating(&inf), UXBinary::zero());
}
#[test]
fn uxbinary_try_from_xbinary_works() {
let pos_finite = XBinary::Finite(bin(5, 2));
let result = UXBinary::try_from_xbinary(&pos_finite);
assert!(result.is_ok());
let neg_finite = XBinary::Finite(bin(-5, 2));
let result = UXBinary::try_from_xbinary(&neg_finite);
assert!(result.is_err());
let pos_inf = XBinary::PosInf;
let result = UXBinary::try_from_xbinary(&pos_inf);
assert!(result.is_ok());
assert_eq!(result.expect("should succeed"), UXBinary::Inf);
let neg_inf = XBinary::NegInf;
let result = UXBinary::try_from_xbinary(&neg_inf);
assert!(result.is_err());
}
#[test]
fn xbinary_from_uxbinary_works() {
let ubx = UXBinary::Finite(ubin(7, 3));
let xb = XBinary::from(ubx);
assert_eq!(xb, XBinary::Finite(bin(7, 3)));
assert_eq!(XBinary::from(UXBinary::Inf), XBinary::PosInf);
}
#[test]
fn abs_distance_finite_cases() {
let one = xbin(1, 0);
let three = xbin(3, 0);
let width = one.clone().abs_distance(three.clone());
assert_eq!(width, UXBinary::Finite(ubin(1, 1)));
let width = one.clone().abs_distance(one.clone());
assert_eq!(width, UXBinary::zero());
let width = three.abs_distance(one);
assert_eq!(width, UXBinary::Finite(ubin(1, 1)));
}
#[test]
fn abs_distance_infinite_cases() {
let one = xbin(1, 0);
let neg_inf = XBinary::NegInf;
let pos_inf = XBinary::PosInf;
assert_eq!(neg_inf.clone().abs_distance(one.clone()), UXBinary::Inf);
assert_eq!(one.clone().abs_distance(pos_inf.clone()), UXBinary::Inf);
assert_eq!(neg_inf.clone().abs_distance(pos_inf.clone()), UXBinary::Inf);
assert_eq!(neg_inf.clone().abs_distance(neg_inf), UXBinary::zero());
assert_eq!(pos_inf.clone().abs_distance(pos_inf), UXBinary::zero());
}
#[test]
fn uxbinary_mul_works() {
let two = UXBinary::Finite(ubin(1, 1));
let three = UXBinary::Finite(ubin(3, 0));
let product = two.clone() * three;
assert_eq!(product, UXBinary::Finite(ubin(3, 1)));
assert!((UXBinary::zero() * UXBinary::Inf).is_zero());
assert!((UXBinary::zero() * two.clone()).is_zero());
assert_eq!(UXBinary::Inf * two, UXBinary::Inf);
}
}