levana_perpswap_cosmos/number/
ops.rs

1use super::{Number, Signed, UnsignedDecimal};
2use anyhow::{anyhow, Result};
3use std::{
4    cmp::Ordering,
5    ops::{Add, Div, Mul, Sub},
6};
7
8// Intentionally keeping operations pegged to Number for now.
9//
10// The whole point of the newtype wrappers is to ensure the mathematical
11// operations we perform are logical. Providing a general purpose `checked_mul`
12// that multiplies two collaterals together would defeat the whole purpose of
13// the exercise. Going forward, we'll add type-specific operations.
14//
15// Addition and subtraction, however, can be added. All our numeric newtypes can
16// be added and subtracted sanely.
17
18impl<T: UnsignedDecimal> Signed<T> {
19    /// Addition that checks for integer overflow.
20    pub fn checked_add(self, rhs: Self) -> Result<Self> {
21        Ok(match (self.is_negative(), rhs.is_negative()) {
22            (false, false) => Self::new_positive(self.value().checked_add(rhs.value())?),
23            (true, true) => Self::new_negative(self.value().checked_add(rhs.value())?),
24            (false, true) => {
25                if self.value() >= rhs.value() {
26                    Self::new_positive(self.value().checked_sub(rhs.value())?)
27                } else {
28                    Self::new_negative(rhs.value().checked_sub(self.value())?)
29                }
30            }
31            (true, false) => {
32                if self.value() >= rhs.value() {
33                    Self::new_negative(self.value().checked_sub(rhs.value())?)
34                } else {
35                    Self::new_positive(rhs.value().checked_sub(self.value())?)
36                }
37            }
38        })
39    }
40
41    /// Subtraction that checks for underflow
42    pub fn checked_sub(self, rhs: Self) -> Result<Self> {
43        self.checked_add(-rhs)
44    }
45}
46
47impl Number {
48    /// Multiplication that checks for integer overflow
49    pub fn checked_mul(self, rhs: Self) -> Result<Self> {
50        match self.value().checked_mul(rhs.value()).ok() {
51            None => Err(anyhow!(
52                "Overflow while multiplying {} and {}",
53                self.value(),
54                rhs.value()
55            )),
56            Some(value) => Ok(if self.is_negative() == rhs.is_negative() {
57                Signed::new_positive(value)
58            } else {
59                Signed::new_negative(value)
60            }),
61        }
62    }
63
64    /// Division that checks for underflow and divide-by-zero.
65    pub fn checked_div(self, rhs: Self) -> Result<Self> {
66        if rhs.is_zero() {
67            Err(anyhow!("Cannot divide with zero"))
68        } else {
69            match self.value().checked_div(rhs.value()).ok() {
70                None => Err(anyhow!(
71                    "Overflow while dividing {} by {}",
72                    self.value(),
73                    rhs.value()
74                )),
75                Some(value) => Ok(if self.is_negative() == rhs.is_negative() {
76                    Signed::new_positive(value)
77                } else {
78                    Signed::new_negative(value)
79                }),
80            }
81        }
82    }
83
84    /// equality check with allowance for precision diff
85    pub fn approx_eq(self, other: Number) -> Result<bool> {
86        Ok((self - other)?.abs() < Self::EPS_E7)
87    }
88
89    /// equality check with allowance for precision diff
90    pub fn approx_eq_eps(self, other: Number, eps: Number) -> Result<bool> {
91        Ok((self - other)?.abs() < eps)
92    }
93
94    /// less-than with allowance for precision diff
95    pub fn approx_lt_relaxed(self, other: Number) -> Result<bool> {
96        Ok(self < (other + Self::EPS_E7)?)
97    }
98
99    /// greater-than with allowance for precision diff
100    pub fn approx_gt_relaxed(self, other: Number) -> Result<bool> {
101        Ok(self > (other - Self::EPS_E7)?)
102    }
103
104    /// greater-than with restriction for precision diff
105    pub fn approx_gt_strict(self, other: Number) -> Result<bool> {
106        Ok(self > (other + Self::EPS_E7)?)
107    }
108}
109
110impl Mul for Number {
111    type Output = anyhow::Result<Self>;
112
113    fn mul(self, rhs: Self) -> Self::Output {
114        self.checked_mul(rhs)
115    }
116}
117
118impl Mul<u64> for Number {
119    type Output = anyhow::Result<Self>;
120
121    fn mul(self, rhs: u64) -> Self::Output {
122        self.checked_mul(rhs.into())
123    }
124}
125
126impl Div<u64> for Number {
127    type Output = anyhow::Result<Self>;
128
129    fn div(self, rhs: u64) -> Self::Output {
130        self.checked_div(rhs.into())
131    }
132}
133
134impl Div for Number {
135    type Output = anyhow::Result<Self>;
136
137    fn div(self, rhs: Self) -> Self::Output {
138        self.checked_div(rhs)
139    }
140}
141
142impl<T: UnsignedDecimal> Add for Signed<T> {
143    type Output = anyhow::Result<Self>;
144
145    fn add(self, rhs: Self) -> Self::Output {
146        self.checked_add(rhs)
147    }
148}
149
150impl<T: UnsignedDecimal> Sub for Signed<T> {
151    type Output = anyhow::Result<Self>;
152
153    fn sub(self, rhs: Self) -> Self::Output {
154        self.checked_sub(rhs)
155    }
156}
157
158impl<T: UnsignedDecimal> std::cmp::PartialOrd for Signed<T> {
159    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
160        Some(self.cmp(other))
161    }
162}
163
164impl<T: UnsignedDecimal> std::cmp::Ord for Signed<T> {
165    fn cmp(&self, other: &Self) -> Ordering {
166        match (self.is_positive_or_zero(), other.is_positive_or_zero()) {
167            (true, true) => self.value().cmp(&other.value()),
168            (false, false) => other.value().cmp(&self.value()),
169            (true, false) => Ordering::Greater,
170            (false, true) => Ordering::Less,
171        }
172    }
173}