jcm/
denomination.rs

1use std::{cmp, fmt, mem};
2
3use crate::{Error, Result};
4
5const DENOM_INT_SHIFT: u8 = 8;
6const DENOM_EXP_MAX: u32 = 19;
7const DENOM_BASE: u64 = 10;
8
9/// Represents the byte length of the [Denomination].
10pub const DENOM_LEN: usize = 2;
11
12/// Represents the currency denomination.
13///
14/// ## Format
15///
16/// Field  | Integer | Exponent
17/// -------|---------|---------
18/// Length | 1 byte  | 1 byte
19///
20/// Denominations representable by a [`u8`] will have a zero exponent, e.g. `100 = 100 * 10^0`.
21///
22/// Any denomination above [`u8::MAX`] will have a non-zero exponent, e.g. `500 = 50 * 10^1`.
23#[repr(C)]
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct Denomination(u16);
26
27impl Denomination {
28    /// Creates a new [Denomination].
29    pub const fn new() -> Self {
30        Self(0x0100)
31    }
32
33    /// Gets the `integer` field of the [Denomination].
34    pub const fn integer(&self) -> u8 {
35        (self.0 >> DENOM_INT_SHIFT) as u8
36    }
37
38    /// Gets the `exponent` field of the [Denomination].
39    pub const fn exponent(&self) -> u8 {
40        self.0 as u8
41    }
42
43    /// Gets the value of the [Denomination].
44    ///
45    /// # Example
46    ///
47    /// ```
48    /// use jcm::Denomination;
49    ///
50    /// let denom = Denomination::new();
51    /// assert_eq!(denom.integer(), 1);
52    /// assert_eq!(denom.exponent(), 0);
53    /// assert_eq!(denom.value(), 1);
54    ///
55    /// let denom = Denomination::from_value(500);
56    /// assert_eq!(denom.integer(), 50);
57    /// assert_eq!(denom.exponent(), 1);
58    /// assert_eq!(denom.value(), 500);
59    /// ```
60    pub fn value(&self) -> u64 {
61        let exp = cmp::min(self.exponent() as u32, DENOM_EXP_MAX);
62        (self.integer() as u64).saturating_mul(DENOM_BASE.pow(exp))
63    }
64
65    /// Infallible function that converts a value into a [Denomination].
66    ///
67    /// # Example
68    ///
69    /// ```
70    /// use jcm::Denomination;
71    ///
72    /// let denom = Denomination::from_value(2000);
73    /// assert_eq!(denom.integer(), 200);
74    /// assert_eq!(denom.exponent(), 1);
75    /// assert_eq!(denom.value(), 2000);
76    /// ```
77    pub fn from_value(val: u64) -> Self {
78        match val {
79            v if v <= u8::MAX as u64 => Self((val << 8) as u16),
80            v if v % 10 == 0 => {
81                let exp = (val as f64).log10().floor() as u32;
82                let (int, exp) = match val.saturating_div(10u64.pow(exp)) {
83                    i if i == 1 || i == 2 => (i * 100, exp - 2),
84                    i if i == 5 || i == 25 => (i * 10, exp - 1),
85                    i => (i, exp),
86                };
87
88                Self(((int << 8) as u16) | exp as u16)
89            }
90            _ => Self(0),
91        }
92    }
93
94    /// Gets the length of the [Denomination].
95    pub const fn len() -> usize {
96        mem::size_of::<u16>()
97    }
98
99    /// Gets whether the [Denomination] is empty.
100    pub const fn is_empty(&self) -> bool {
101        self.0 == 0
102    }
103
104    /// Gets whether the [Denomination] is valid.
105    pub const fn is_valid(&self) -> bool {
106        matches!(self.integer(), 1 | 2 | 5 | 10 | 20 | 50 | 100 | 200 | 250)
107    }
108
109    /// Converts the [Denomination] to a [`u16`].
110    pub const fn to_u16(&self) -> u16 {
111        self.0
112    }
113
114    /// Converts the [Denomination] to a [`u16`].
115    pub const fn into_u16(self) -> u16 {
116        self.0
117    }
118
119    /// Infallible function to convert a byte buffer into a [Denomination].
120    pub fn from_bytes(val: &[u8]) -> Self {
121        match val.len() {
122            0 => Self(0),
123            1 => Self((val[0] as u16) << DENOM_INT_SHIFT),
124            _ => Self(((val[0] as u16) << DENOM_INT_SHIFT) | val[1] as u16),
125        }
126    }
127
128    /// Gets whether the value is a valid [Denomination].
129    pub fn valid_value(val: u64) -> bool {
130        [1, 2, 5, 10, 20, 50, 100, 200, 250]
131            .into_iter()
132            .any(|v| val % v == 0 && (val <= 10 || val % 10 == 0))
133    }
134
135    /// Writes the [Denomination] to a byte buffer.
136    pub fn to_bytes(&self, buf: &mut [u8]) -> Result<()> {
137        let len = Self::len();
138        let buf_len = buf.len();
139
140        if buf_len < len {
141            Err(Error::InvalidDenominationLen((buf_len, len)))
142        } else {
143            buf.copy_from_slice(self.to_u16().to_be_bytes().as_ref());
144            Ok(())
145        }
146    }
147
148    /// Converts a [Denomination] into a byte array.
149    pub const fn into_bytes(self) -> [u8; DENOM_LEN] {
150        self.to_u16().to_be_bytes()
151    }
152}
153
154impl TryFrom<u64> for Denomination {
155    type Error = Error;
156
157    fn try_from(val: u64) -> Result<Self> {
158        match Self::from_value(val) {
159            d if d.is_valid() => Ok(d),
160            d => Err(Error::InvalidDenomination((d.integer(), d.exponent()))),
161        }
162    }
163}
164
165impl TryFrom<u32> for Denomination {
166    type Error = Error;
167
168    fn try_from(val: u32) -> Result<Self> {
169        (val as u64).try_into()
170    }
171}
172
173impl TryFrom<u16> for Denomination {
174    type Error = Error;
175
176    fn try_from(val: u16) -> Result<Self> {
177        (val as u64).try_into()
178    }
179}
180
181impl TryFrom<u8> for Denomination {
182    type Error = Error;
183
184    fn try_from(val: u8) -> Result<Self> {
185        (val as u64).try_into()
186    }
187}
188
189impl TryFrom<&[u8]> for Denomination {
190    type Error = Error;
191
192    fn try_from(val: &[u8]) -> Result<Self> {
193        match Self::from_bytes(val) {
194            d if d.is_valid() => Ok(d),
195            d => Err(Error::InvalidDenomination((d.integer(), d.exponent()))),
196        }
197    }
198}
199
200impl<const N: usize> TryFrom<[u8; N]> for Denomination {
201    type Error = Error;
202
203    fn try_from(val: [u8; N]) -> Result<Self> {
204        val.as_ref().try_into()
205    }
206}
207
208impl<const N: usize> TryFrom<&[u8; N]> for Denomination {
209    type Error = Error;
210
211    fn try_from(val: &[u8; N]) -> Result<Self> {
212        val.as_ref().try_into()
213    }
214}
215
216impl Default for Denomination {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222impl fmt::Display for Denomination {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(f, "{{")?;
225        write!(f, r#""integer": {:#x}, "#, self.integer())?;
226        write!(f, r#""exponent": {:#x}, "#, self.exponent())?;
227        write!(f, r#""value": {:#x}"#, self.value())?;
228        write!(f, "}}")
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_denomination() {
238        let raw_vals = [1, 2, 5, 10, 20, 50, 100, 250, 500, 1000, 10_000u64];
239        let exp_ints = [1, 2, 5, 10, 20, 50, 100, 250, 50, 100, 100];
240        let exp_exps = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2];
241        let exp_denoms = [
242            Denomination(0x0100),
243            Denomination(0x0200),
244            Denomination(0x0500),
245            Denomination(0x0a00),
246            Denomination(0x1400),
247            Denomination(0x3200),
248            Denomination(0x6400),
249            Denomination(0xfa00),
250            Denomination(0x3201),
251            Denomination(0x6401),
252            Denomination(0x6402),
253        ];
254
255        raw_vals.into_iter().enumerate().for_each(|(i, val)| {
256            assert_eq!(Denomination::try_from(val), Ok(exp_denoms[i]));
257            assert_eq!(
258                Denomination::try_from([exp_ints[i], exp_exps[i]]),
259                Ok(exp_denoms[i])
260            );
261
262            let denom = Denomination::from_value(val);
263
264            assert_eq!(denom, exp_denoms[i]);
265            assert_eq!(denom.integer(), exp_ints[i]);
266            assert_eq!(denom.exponent(), exp_exps[i]);
267            assert_eq!(denom.value(), val);
268
269            assert!(denom.is_valid());
270            assert!(!denom.is_empty());
271        });
272    }
273
274    #[test]
275    fn test_denomination_invalid() {
276        let zero_denom = Denomination::from_value(0);
277
278        assert!(zero_denom.is_empty());
279        assert!(!zero_denom.is_valid());
280
281        (0..=u16::MAX)
282            .filter(|&v| !Denomination::valid_value(v as u64))
283            .for_each(|val| {
284                assert!(!Denomination::from_value(val as u64).is_valid());
285                assert!(Denomination::try_from(val).is_err());
286            });
287    }
288}