1use std::fmt;
8
9use rust_decimal::Decimal;
10use serde::{Deserialize, Serialize};
11
12use crate::Fraction;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
18pub struct Bps(Decimal);
19
20#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22pub enum BpsError {
23 #[error("basis points {0} out of range [0, 10000]")]
25 OutOfRange(Decimal),
26}
27
28impl Bps {
29 pub fn new(value: Decimal) -> Result<Self, BpsError> {
31 if value < Decimal::ZERO || value > Decimal::from(10000) {
32 return Err(BpsError::OutOfRange(value));
33 }
34 Ok(Self(value))
35 }
36
37 pub fn from_trusted(value: u32) -> Self {
43 assert!(value <= 10000, "from_trusted: {value} > 10000");
44 Self(Decimal::from(value))
45 }
46
47 pub fn value(&self) -> Decimal {
49 self.0
50 }
51
52 pub fn as_fraction(&self) -> Decimal {
56 self.0 / Decimal::from(10000)
57 }
58
59 pub fn to_fraction(&self) -> Fraction {
61 Fraction(self.0 / Decimal::from(10000))
63 }
64
65 pub fn zero() -> Self {
67 Self(Decimal::ZERO)
68 }
69}
70
71impl fmt::Display for Bps {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "{}bps", self.0.normalize())
74 }
75}
76
77#[cfg(test)]
78#[path = "bps_tests.rs"]
79mod tests;