pub mod calculation;
pub mod constructor;
use std::cmp::Ordering;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait CurrencyLocale {
fn separator(&self) -> char;
fn thousand_separator(&self) -> char;
fn currency_symbol(&self) -> &'static str;
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Currency<L: CurrencyLocale> {
negative: bool,
amount: usize,
locale: L,
}
impl<L: CurrencyLocale + PartialEq> PartialOrd for Currency<L> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.locale != other.locale {
return None;
}
match (
self.negative,
other.negative,
self.amount.cmp(&other.amount),
) {
(true, false, Ordering::Less)
| (true, false, Ordering::Equal)
| (true, false, Ordering::Greater)
| (false, false, Ordering::Less)
| (true, true, Ordering::Greater) => Some(Ordering::Less),
(false, false, Ordering::Equal) | (true, true, Ordering::Equal) => {
Some(Ordering::Equal)
}
(false, true, Ordering::Less)
| (false, true, Ordering::Equal)
| (false, false, Ordering::Greater)
| (false, true, Ordering::Greater)
| (true, true, Ordering::Less) => Some(Ordering::Greater),
}
}
}
impl<L> std::fmt::Display for Currency<L>
where
L: CurrencyLocale,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buffer = self.full().to_string();
if buffer.len() > 3 {
let len = buffer.len() - 2;
for idx in (1..len).rev().step_by(3) {
buffer.insert(idx, self.locale.thousand_separator());
}
}
if self.negative {
write!(f, "-")?;
}
write!(
f,
"{}{}{:02} {}",
buffer,
self.locale.separator(),
self.part(),
self.locale.currency_symbol()
)
}
}
impl<L: CurrencyLocale> Currency<L> {
#[must_use]
pub fn new(negative: bool, amount: usize, locale: L) -> Self {
Self {
negative,
amount,
locale,
}
}
#[must_use]
pub fn with_locale(mut self, locale: L) -> Self {
self.locale = locale;
self
}
#[inline]
pub fn full(&self) -> usize {
self.amount / 100
}
#[inline]
pub fn part(&self) -> usize {
self.amount % 100
}
#[inline]
pub fn amount(&self) -> isize {
let amount = self.amount as isize;
if self.negative {
-amount
} else {
amount
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Clone, Copy, Default, Debug, PartialEq)]
enum CurrencyL {
#[default]
De,
}
impl CurrencyLocale for CurrencyL {
fn separator(&self) -> char {
','
}
fn thousand_separator(&self) -> char {
'.'
}
fn currency_symbol(&self) -> &'static str {
"€"
}
}
#[test]
fn print_currency() {
let mut curr = Currency::new(false, 0, CurrencyL::De);
for (full, full_string) in [
(2_00, "2"),
(20_00, "20"),
(200_00, "200"),
(2000_00, "2.000"),
(20_000_00, "20.000"),
(200_000_00, "200.000"),
(2_000_000_00, "2.000.000"),
(20_000_000_00, "20.000.000"),
(200_000_000_00, "200.000.000"),
] {
curr.amount = full;
assert_eq!(format!("{full_string},00 €"), curr.to_string());
}
let curr = Currency::new(false, 202, CurrencyL::De);
assert_eq!("2,02 €", &curr.to_string());
}
#[test]
fn construct_f32() {
let first_val = 100.8_f32;
let second_val = 191.0_f32;
let expected = Currency::<CurrencyL>::from(first_val + second_val);
assert_eq!(expected, Currency::new(false, 291_80, CurrencyL::De));
}
#[test]
fn compare_both_negative_equal() {
let curr1 = Currency::new(true, 2_22, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 == curr2);
}
#[test]
fn compare_both_negative_equal_full_diff_part() {
let curr1 = Currency::new(true, 2_21, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_both_negative_diff_full_equal_part() {
let curr1 = Currency::new(true, 1_22, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_both_negative_diff_full_greater_part() {
let curr1 = Currency::new(true, 1_89, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_diff_negative_equal() {
let curr1 = Currency::new(false, 2_22, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_diff_negative_equal_full_diff_part() {
let curr1 = Currency::new(false, 2_24, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_diff_negative_greater_values() {
let curr1 = Currency::new(false, 1_11, CurrencyL::De);
let curr2 = Currency::new(true, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_equal_full_less_part() {
let curr1 = Currency::new(false, 2_21, CurrencyL::De);
let curr2 = Currency::new(false, 2_22, CurrencyL::De);
assert!(curr1 < curr2);
}
#[test]
fn compare_equal_full_greater_part() {
let curr1 = Currency::new(false, 2_23, CurrencyL::De);
let curr2 = Currency::new(false, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_diff_full_equal_part() {
let curr1 = Currency::new(false, 3_22, CurrencyL::De);
let curr2 = Currency::new(false, 2_22, CurrencyL::De);
assert!(curr1 > curr2);
}
#[test]
fn compare_diff_full_greater_part() {
let curr1 = Currency::new(false, 3_22, CurrencyL::De);
let curr2 = Currency::new(false, 2_89, CurrencyL::De);
assert!(curr1 > curr2);
}
}