risq 0.4.1

Re-implementation of Bisq (https://github.com/bisq-network/bisq) in rust
Documentation
use std::cmp::Ordering;
use std::ops::*;

#[cfg(feature = "statistics")]
pub const ZERO: NumberWithPrecision = NumberWithPrecision {
    base_amount: 0,
    precision: 8,
};
#[derive(Eq, Clone, Debug, Copy)]
pub struct NumberWithPrecision {
    base_amount: u64,
    precision: u32,
}

impl NumberWithPrecision {
    pub fn new(base_amount: u64, precision: u32) -> Self {
        Self {
            base_amount,
            precision,
        }
    }

    pub fn format(&self, target_precision: u32) -> String {
        let mut ret = String::new();
        let mut rest_amount = self.base_amount;

        if target_precision > self.precision {
            rest_amount *= 10_u64.pow(target_precision - self.precision);
        } else if self.precision > target_precision {
            rest_amount /= 10_u64.pow(self.precision - target_precision);
        }

        while ret.len() < target_precision as usize {
            ret.push(char_of_last_digit(rest_amount));
            rest_amount /= 10;
        }
        ret.push('.');

        while rest_amount > 0 {
            ret.push(char_of_last_digit(rest_amount));
            rest_amount /= 10;
        }
        if ret.len() == target_precision as usize + 1 {
            ret.push('0');
        }
        ret.chars().rev().collect()
    }

    pub fn with_precision(&self, target_precision: u32) -> Self {
        let mut rest_amount = self.base_amount;
        if target_precision > self.precision {
            rest_amount *= 10_u64.pow(target_precision - self.precision);
        } else if self.precision > target_precision {
            rest_amount /= 10_u64.pow(self.precision - target_precision);
        }
        Self::new(rest_amount, target_precision)
    }
}

fn char_of_last_digit(n: u64) -> char {
    match n % 10 {
        0 => '0',
        1 => '1',
        2 => '2',
        3 => '3',
        4 => '4',
        5 => '5',
        6 => '6',
        7 => '7',
        8 => '8',
        9 => '9',
        _ => unimplemented!(),
    }
}

impl Mul for NumberWithPrecision {
    type Output = Self;

    #[allow(clippy::suspicious_arithmetic_impl)]
    fn mul(self, right: Self) -> Self {
        let (left_precision, right_precision) = (self.precision, right.precision);
        let (mut left_value, mut right_value) = (self.base_amount, right.base_amount);
        let mut res_precision = left_precision + right_precision;
        let target_precision = u32::max(left_precision, right_precision);

        while res_precision > target_precision {
            if left_value % 10 == 0 {
                left_value /= 10;
                res_precision -= 1;
            } else {
                break;
            }
        }
        while res_precision > target_precision {
            if right_value % 10 == 0 {
                right_value /= 10;
                res_precision -= 1;
            } else {
                break;
            }
        }
        let mut res = left_value * right_value;
        if res_precision > target_precision {
            res /= 10_u64.pow(res_precision - right_precision);
        } else if res_precision < target_precision {
            res *= 10_u64.pow(right_precision - res_precision);
        }
        NumberWithPrecision::new(res, target_precision)
    }
}
impl Div<u64> for NumberWithPrecision {
    type Output = Self;

    #[allow(clippy::suspicious_arithmetic_impl)]
    fn div(self, rhs: u64) -> Self::Output {
        if rhs == 0 {
            panic!("Cannot divide by zero-valued `NumberWithPrecision`!");
        }
        NumberWithPrecision::new(self.base_amount / rhs, self.precision)
    }
}
impl Div<NumberWithPrecision> for NumberWithPrecision {
    type Output = Self;

    fn div(self, other: NumberWithPrecision) -> Self::Output {
        if other.base_amount == 0 {
            panic!("Cannot divide by zero-valued `NumberWithPrecision`!");
        }
        let target_precision = self.precision.max(other.precision);
        let f1 = self.base_amount as f64 / 10_u32.pow(self.precision) as f64;
        let f2 = other.base_amount as f64 / 10_u32.pow(other.precision) as f64;
        NumberWithPrecision::new(
            ((f1 / f2) * 10_f64.powf(target_precision as f64)) as u64,
            target_precision,
        )
    }
}

impl AddAssign for NumberWithPrecision {
    fn add_assign(&mut self, other: Self) {
        let target_precision = self.precision.max(other.precision);
        *self = self.with_precision(target_precision);
        self.base_amount += other.with_precision(target_precision).base_amount;
    }
}

impl PartialEq for NumberWithPrecision {
    fn eq(&self, other: &NumberWithPrecision) -> bool {
        let target_precision = self.precision.max(other.precision);
        self.with_precision(target_precision).base_amount
            == other.with_precision(target_precision).base_amount
    }
}
impl PartialOrd for NumberWithPrecision {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for NumberWithPrecision {
    fn cmp(&self, other: &Self) -> Ordering {
        let target_precision = self.precision.max(other.precision);
        self.with_precision(target_precision)
            .base_amount
            .cmp(&other.with_precision(target_precision).base_amount)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn multiply() {
        let left_precision = 4;
        let right_precision = 8;

        let price_base = 9000 * 10_u64.pow(left_precision);
        let amount_base = 1 * 10_u64.pow(right_precision);
        let price = NumberWithPrecision::new(price_base, left_precision);
        let amount = NumberWithPrecision::new(amount_base, right_precision);

        let high_volume = price * amount;
        let low_volume = price * amount / 10000;
        assert!(&high_volume.format(8) == "9000.00000000");
        assert!(&low_volume.format(8) == "0.90000000");
    }

    #[test]
    fn add_assign() {
        let mut amount = NumberWithPrecision::new(0, 8);
        amount += NumberWithPrecision::new(10000, 4);
        assert!(amount == NumberWithPrecision::new(1, 0));
        amount += NumberWithPrecision::new(123456789, 8);
        assert!(amount == NumberWithPrecision::new(2234567890, 9));
    }
}