decimal_percentage/
lib.rs

1//! # Percentage Type with Decimal
2//!
3//! [![Build Status](https://github.com/fMeow/decimal-percentage-rs/workflows/Rust/badge.svg)](https://github.com/fMeow/decimal-percentage-rs/actions)
4//! [![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
5//! [![Crates.io](https://img.shields.io/crates/v/decimal-percentage.svg)](https://crates.io/crates/decimal-percentage)
6//! [![decimal-percentage](https://docs.rs/decimal-percentage/badge.svg)](https://docs.rs/decimal-percentage)
7//!
8//! A type to represent percentage with high precision thanks to `rust_decimal`.
9//!
10//! A percentage can perform addition, subtraction and multiplication.
11//!
12//! ```rust
13//! # use std::convert::TryFrom;
14//! # use rust_decimal::prelude::{FromPrimitive, FromStr};
15//! use decimal_percentage::Percentage;
16//! use rust_decimal::Decimal;
17//!
18//! let p1 = Percentage::from(0.1f64);
19//! let p2 = Percentage::from(0.1f32);
20//! let p3 = Percentage::try_from("0.1").unwrap();
21//! let p4 = Percentage::from(Decimal::from_f64(0.3).unwrap());
22//!
23//! assert_eq!(p1 + p2, Percentage::from(0.2));
24//! assert_eq!(p1 + 0.2, Percentage::from(0.3));
25//! assert_eq!(p4 - p2, Percentage::from(0.2));
26//! assert_eq!(p1 * 66.0, 6.6);
27//! assert_eq!(p1 * 100u32, 10u32);
28//! assert_eq!(p1 * -100i32, -10i32);
29//! // note that a multiplication to integer type can lose precision
30//! assert_eq!(p1 * -33i32, -3i32);
31//! // multiplication on extremely small value with Decimal,
32//! // that is not representable with float point
33//! let small_value = Decimal::from_str("0.0000000000000000002").unwrap();
34//! assert_eq!(p1 * small_value, Decimal::from_str("0.00000000000000000002").unwrap());
35//! ```
36//!
37//! ## Contributing
38//! Contributions and feed back are welcome following Github workflow.
39//!
40//! ## License
41//! `decimal_percentage` is provided under the MIT license. See [LICENSE](./LICENSE).
42use std::convert::TryFrom;
43use std::fmt;
44use std::fmt::Formatter;
45use std::ops::{Add, Mul, Sub};
46use std::str::FromStr;
47
48use rust_decimal::prelude::*;
49use rust_decimal::Decimal;
50use serde::{Deserialize, Serialize};
51
52/// Percentage Type
53///
54/// - Example:
55/// ```rust
56/// # use decimal_percentage::Percentage;
57/// # use std::convert::TryFrom;
58/// let p1 = Percentage::from(0.1f64);
59/// let p2 = Percentage::from(0.1f32);
60/// let p3 = Percentage::try_from("0.1").unwrap();
61///
62/// assert_eq!(p1 + p2, Percentage::from(0.2));
63/// assert_eq!(p1 + 0.2, Percentage::from(0.3));
64/// ```
65#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
66pub struct Percentage(Decimal);
67
68impl From<Decimal> for Percentage {
69    fn from(p: Decimal) -> Self {
70        Percentage(p)
71    }
72}
73
74impl From<f64> for Percentage {
75    fn from(p: f64) -> Self {
76        Percentage(Decimal::from_f64(p).unwrap())
77    }
78}
79
80impl From<f32> for Percentage {
81    fn from(p: f32) -> Self {
82        Percentage(Decimal::from_f32(p).unwrap())
83    }
84}
85
86impl TryFrom<&str> for Percentage {
87    type Error = rust_decimal::Error;
88    fn try_from(p: &str) -> Result<Self, Self::Error> {
89        Ok(Percentage(Decimal::from_str(p)?))
90    }
91}
92
93impl FromStr for Percentage {
94    type Err = rust_decimal::Error;
95    fn from_str(p: &str) -> Result<Self, Self::Err> {
96        Self::try_from(p)
97    }
98}
99
100impl fmt::Debug for Percentage {
101    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102        f.write_str(&format!("{}%", self.0 * Decimal::from(100)))
103    }
104}
105
106impl fmt::Display for Percentage {
107    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
108        f.write_str(&format!("{:?}", self))
109    }
110}
111
112impl Mul for Percentage {
113    type Output = Self;
114    fn mul(self, rhs: Self) -> Self::Output {
115        Percentage::from(self.0 * rhs.0)
116    }
117}
118
119impl<'a, 'b> Mul<&'a Percentage> for &'b Percentage {
120    type Output = Percentage;
121    fn mul(self, rhs: &'a Percentage) -> Self::Output {
122        Percentage::from(rhs.0 * self.0)
123    }
124}
125
126impl Mul<Decimal> for Percentage {
127    type Output = Decimal;
128    fn mul(self, rhs: Decimal) -> Self::Output {
129        self.0 * rhs
130    }
131}
132
133impl Mul<Percentage> for Decimal {
134    type Output = Decimal;
135    fn mul(self, rhs: Percentage) -> Self::Output {
136        rhs * self
137    }
138}
139
140macro_rules! impl_mul {
141    ($T:ty, $from_ty:path, $to_ty:path) => {
142        impl Mul<$T> for Percentage {
143            type Output = $T;
144            fn mul(self, rhs: $T) -> Self::Output {
145                let d: Decimal = $from_ty(rhs).unwrap();
146                $to_ty(&(self.0 * d)).unwrap()
147            }
148        }
149        impl Mul<Percentage> for $T {
150            type Output = $T;
151            fn mul(self, rhs: Percentage) -> Self::Output {
152                let d: Decimal = $from_ty(self).unwrap();
153                $to_ty(&(rhs.0 * d)).unwrap()
154            }
155        }
156    };
157}
158
159impl_mul!(isize, FromPrimitive::from_isize, ToPrimitive::to_isize);
160impl_mul!(i8, FromPrimitive::from_i8, ToPrimitive::to_i8);
161impl_mul!(i16, FromPrimitive::from_i16, ToPrimitive::to_i16);
162impl_mul!(i32, FromPrimitive::from_i32, ToPrimitive::to_i32);
163impl_mul!(i64, FromPrimitive::from_i64, ToPrimitive::to_i64);
164impl_mul!(usize, FromPrimitive::from_usize, ToPrimitive::to_usize);
165impl_mul!(u8, FromPrimitive::from_u8, ToPrimitive::to_u8);
166impl_mul!(u16, FromPrimitive::from_u16, ToPrimitive::to_u16);
167impl_mul!(u32, FromPrimitive::from_u32, ToPrimitive::to_u32);
168impl_mul!(u64, FromPrimitive::from_u64, ToPrimitive::to_u64);
169impl_mul!(f64, FromPrimitive::from_f64, ToPrimitive::to_f64);
170impl_mul!(f32, FromPrimitive::from_f32, ToPrimitive::to_f32);
171
172impl Add for Percentage {
173    type Output = Self;
174    fn add(self, rhs: Self) -> Self::Output {
175        let sum = self.0 + rhs.0;
176
177        Percentage::from(sum)
178    }
179}
180
181impl Add<Decimal> for Percentage {
182    type Output = Self;
183    fn add(self, rhs: Decimal) -> Self::Output {
184        self + Percentage::from(rhs)
185    }
186}
187
188impl Add<Percentage> for Decimal {
189    type Output = Percentage;
190    fn add(self, rhs: Percentage) -> Self::Output {
191        rhs + Percentage::from(self)
192    }
193}
194
195macro_rules! impl_add {
196    ($T:ty, $from_ty:path) => {
197        impl Add<$T> for Percentage {
198            type Output = Percentage;
199            fn add(self, rhs: $T) -> Self::Output {
200                let d: Decimal = $from_ty(rhs).unwrap();
201                self + Percentage::from(d)
202            }
203        }
204        impl Add<Percentage> for $T {
205            type Output = Percentage;
206            fn add(self, rhs: Percentage) -> Self::Output {
207                let d: Decimal = $from_ty(self).unwrap();
208                rhs + Percentage::from(d)
209            }
210        }
211    };
212}
213
214impl_add!(isize, FromPrimitive::from_isize);
215impl_add!(i8, FromPrimitive::from_i8);
216impl_add!(i16, FromPrimitive::from_i16);
217impl_add!(i32, FromPrimitive::from_i32);
218impl_add!(i64, FromPrimitive::from_i64);
219impl_add!(usize, FromPrimitive::from_usize);
220impl_add!(u8, FromPrimitive::from_u8);
221impl_add!(u16, FromPrimitive::from_u16);
222impl_add!(u32, FromPrimitive::from_u32);
223impl_add!(u64, FromPrimitive::from_u64);
224impl_add!(f64, FromPrimitive::from_f64);
225impl_add!(f32, FromPrimitive::from_f32);
226
227impl Sub for Percentage {
228    type Output = Self;
229
230    fn sub(self, rhs: Self) -> Self::Output {
231        let dif = self.0 - rhs.0;
232
233        Percentage::from(dif)
234    }
235}
236
237macro_rules! impl_sub {
238    ($T:ty, $from_ty:path) => {
239        impl Sub<$T> for Percentage {
240            type Output = Percentage;
241            fn sub(self, rhs: $T) -> Self::Output {
242                let d: Decimal = $from_ty(rhs).unwrap();
243                self - Percentage::from(d)
244            }
245        }
246        impl Sub<Percentage> for $T {
247            type Output = Percentage;
248            fn sub(self, rhs: Percentage) -> Self::Output {
249                let d: Decimal = $from_ty(self).unwrap();
250                Percentage::from(d) - rhs
251            }
252        }
253    };
254}
255
256impl_sub!(isize, FromPrimitive::from_isize);
257impl_sub!(i8, FromPrimitive::from_i8);
258impl_sub!(i16, FromPrimitive::from_i16);
259impl_sub!(i32, FromPrimitive::from_i32);
260impl_sub!(i64, FromPrimitive::from_i64);
261impl_sub!(usize, FromPrimitive::from_usize);
262impl_sub!(u8, FromPrimitive::from_u8);
263impl_sub!(u16, FromPrimitive::from_u16);
264impl_sub!(u32, FromPrimitive::from_u32);
265impl_sub!(u64, FromPrimitive::from_u64);
266impl_sub!(f64, FromPrimitive::from_f64);
267impl_sub!(f32, FromPrimitive::from_f32);
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn percentage() {
275        let p1 = Percentage::from(0.5);
276        let p2 = Percentage::try_from("0.00000015").unwrap();
277        let p3 = Percentage::from(0.2);
278        let p4 = Percentage::from(1.2);
279
280        assert_eq!(1 - p1, Percentage::from(0.5));
281        assert_eq!(p1 - 0.2, Percentage::from(0.3));
282        assert_eq!(1u8 - p1, Percentage::from(0.5));
283        assert_eq!(p1 - 0.2f32, Percentage::from(0.3));
284        assert_eq!(1 + p1, Percentage::from(1.5));
285        assert_eq!(p1 + 1, Percentage::from(1.5));
286
287        assert_eq!(p1 - p3, Percentage::from(0.3));
288        assert_eq!(p1 + p2, Percentage::try_from("0.50000015").unwrap());
289        assert_eq!(p4 - p3, 1.0.into());
290
291        assert_eq!(p1 * 100, 50);
292        assert_eq!(p1 * 100u8, 50u8);
293        assert_eq!(p1 * -100i8, -50i8);
294        assert_eq!(p1 * 100u16, 50u16);
295        assert_eq!(p1 * -100i16, -50i16);
296        assert_eq!(p1 * 100u32, 50u32);
297        assert_eq!(p1 * -100i32, -50i32);
298        assert_eq!(p1 * 100u64, 50u64);
299        assert_eq!(p1 * -100i64, -50i64);
300        assert_eq!(p1 * 100usize, 50usize);
301        assert_eq!(p1 * -100isize, -50isize);
302        assert_eq!(p1 * 90.0, 45.0);
303        assert_eq!(p1 * 90.0f32, 45.0f32);
304
305        assert_eq!(100 * p1, 50);
306        assert_eq!(100u8 * p1, 50u8);
307        assert_eq!(-100i8 * p1, -50i8);
308        assert_eq!(100u16 * p1, 50u16);
309        assert_eq!(-100i16 * p1, -50i16);
310        assert_eq!(100u32 * p1, 50u32);
311        assert_eq!(-100i32 * p1, -50i32);
312        assert_eq!(100u64 * p1, 50u64);
313        assert_eq!(-100i64 * p1, -50i64);
314        assert_eq!(100usize * p1, 50usize);
315        assert_eq!(-100isize * p1, -50isize);
316        assert_eq!(90.0 * p1, 45.0);
317        assert_eq!(90.0f32 * p1, 45.0f32);
318    }
319}