primitive_fixed_point_decimal 0.11.0

Primitive fixed-point decimal types.
Documentation
#![no_std]

//! Primitive fixed-point decimal types.
//!
//! For example, `ConstScaleFpdec<i64, 4>` means using `i64` as the underlying
//! representation, and the static scale is `4`.
//!
//!
//! # Features
//!
//! - Fixed-point. The scale is bound to the *type* but not each *value*.
//!
//! - Decimal. Using integer types to represent numbers with a scaling factor
//!   (also called as "scale") in base 10 to achieve the accuracy. This is a
//!   [common idea](https://en.wikipedia.org/wiki/Fixed-point_arithmetic#Representation).
//!
//! - The `+` and `-` operations only perform between same types in same scale.
//!   There is no implicitly type or scale conversion. This makes sense, for we
//!   do not want to add `Balance` type by `Price` type.
//!
//! - The `*` and `/` operations accept operand with different types and scales,
//!   and allow the result's scale specified. Certainly we need to multiply
//!   between `Balance` type and `Price` type.
//!
//! - Supports 2 ways to specify the scale: *const* and *out-of-band*. See
//!   the [Specify Scale](#specify-scale) section for details.
//!
//! - Supports both signed and unsigned types.
//!
//! - Supports scale larger than the significant digits of the underlying integer
//!   type. For example `ConstScaleFpdec<i8, 4>` represents numbers in range
//!   [-0.0128, 0.0127].
//!
//! - Supports negative scale. For example `ConstScaleFpdec<i8, -2>` represents
//!   numbers in range [-12800, 12700] with step 100.
//!
//! - `no-std` and `no-alloc`.
//!
//!
//! # Specify Scale
//!
//! There are 2 ways to specify the scale: *const* and *out-of-band*:
//!
//! - For the *const* type [`ConstScaleFpdec`], we use Rust's *const generics*
//!   to specify the scale. For example, `ConstScaleFpdec<i64, 4>` means
//!   scale is 4.
//!
//! - For the *out-of-band* type [`OobScaleFpdec`], we do NOT save the
//!   scale with decimal types, so it's your job to save it somewhere
//!   and apply it in the following operations later. For example,
//!   `OobScaleFpdec<i64>` takes no scale information.
//!
//! Generally, the *const* type is more convenient and suitable for most
//! scenarios. For example, in traditional currency exchange, you can use
//! `ConstScaleFpdec<i64, 2>` to represent balance, e.g. `1234.56` USD and
//! `8888800.00` JPY. And use `ConstScaleFpdec<u32, 6>` to represent all
//! market prices since 6-digit-scale is big enough for all currency
//! pairs, e.g. `146.4730` JPY/USD and `0.006802` USD/JPY:
//!
//! ```
//! use primitive_fixed_point_decimal::{ConstScaleFpdec, fpdec};
//! type Balance = ConstScaleFpdec<i64, 2>; // 2 is enough for all currencies
//! type Price = ConstScaleFpdec<u32, 6>; // 6 is enough for all markets
//!
//! let usd: Balance = fpdec!(1234.56);
//! let price: Price = fpdec!(146.4730);
//!
//! let jpy: Balance = usd * price;
//! assert_eq!(jpy, fpdec!(180829.71));
//! ```
//!
//! However in some scenarios, such as in cryptocurrency exchange, the
//! price differences across various markets are very significant. For
//! example `81234.0` in BTC/USDT and `0.000004658` in PEPE/USDT. Here
//! we need to select different scales for each market. So it's
//! the *Out-of-band* type:
//!
//! ```
//! use primitive_fixed_point_decimal::{OobScaleFpdec, fpdec};
//! type Balance = OobScaleFpdec<i64>; // no global scale set
//! type Price = OobScaleFpdec<u32>; // no global scale set
//!
//! // each market has its own scale configuration
//! struct Market {
//!     base_asset_scale: i32,
//!     quote_asset_scale: i32,
//!     price_scale: i32,
//! }
//!
//! // let's take BTC/USDT market as example
//! let btc_usdt = Market {
//!     base_asset_scale: 8,
//!     quote_asset_scale: 6,
//!     price_scale: 1,
//! };
//!
//! // we need tell the scale to `fpdec!`
//! let btc: Balance = fpdec!(0.34, btc_usdt.base_asset_scale);
//! let price: Price = fpdec!(81234.0, btc_usdt.price_scale);
//!
//! // we need tell the scale difference to `checked_mul()` method
//! let diff = btc_usdt.base_asset_scale + btc_usdt.price_scale - btc_usdt.quote_asset_scale;
//! let usdt = btc.checked_mul(price, diff).unwrap();
//! assert_eq!(usdt, fpdec!(27619.56, btc_usdt.quote_asset_scale));
//! ```
//!
//! Obviously it's verbose to use, but offers greater flexibility.
//!
//! Another example is the SQL `Decimal` data type.
//! In the server end, the scale of each decimal column is fixed on created
//! (at runtime), so it fits `OobScaleFpdec`.
//! While in the client end, the application knows the business logical and
//! the scale of each decimal column ahead (at compilation time), so it fits
//! `ConstScaleFpdec`.
//!
//!
//! # Features
//!
//! - `serde` enables serde traits integration (`Serialize`/`Deserialize`).

// modules:
//
//     ConstScaleFpdec                             OobScaleFpdec
//            ^                                          ^
//            +---------------\           /--------------+
//            |               |           |              |
// +----------+--------+  +---+-----------+---+  +-------+---------+
// | const_scale_fpdec |  | none_scale_common |  | oob_scale_fpdec |
// +-------------------+  +-------------------+  +-----------------+
// +---------------------------------------------------------------+
// |                fpdec_inner: FpdecInner trait                  |
// +---------------------------------------------------------------+
// +------------------------------------+  +-----------------------+
// |    inner_shorts: i8,i16,i32,i64    |  |   inner_i128: i128    |
// |                  u8,u16,u32,u64    |  |               u128    |
// +------------------------------------+  +-----------------------+
mod const_scale_fpdec;
mod fpdec_inner;
mod inner_i128;
mod inner_shorts;
mod none_scale_common;
mod oob_scale_fpdec;
mod rounding_div;

pub use crate::const_scale_fpdec::ConstScaleFpdec;
pub use crate::fpdec_inner::FpdecInner;
pub use crate::oob_scale_fpdec::{OobFmt, OobScaleFpdec};
pub use crate::rounding_div::Rounding;

/// Error in converting from string.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ParseError {
    /// Empty string.
    Empty,
    /// Invalid digit in the string.
    Invalid,
    /// Overflow.
    Overflow,
    /// Precision out of range.
    Precision,
}

use core::num::{IntErrorKind, ParseIntError};
impl From<ParseIntError> for ParseError {
    fn from(pie: ParseIntError) -> Self {
        match pie.kind() {
            IntErrorKind::Empty => ParseError::Empty,
            IntErrorKind::InvalidDigit => ParseError::Invalid,
            _ => ParseError::Overflow,
        }
    }
}

use core::fmt;
impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            ParseError::Empty => "empty string",
            ParseError::Invalid => "invalid digit in the string",
            ParseError::Overflow => "overflow",
            ParseError::Precision => "precision out of range",
        };
        write!(f, "{s}")
    }
}

impl core::error::Error for ParseError {}

/// Build decimal from integer or float number easily.
///
/// It accepts 1 argument for `ConstScaleFpdec`, and accepts 1 extra
/// argument for `OobScaleFpdec`, the out-of-band scale of course.
///
/// Panics:
///
/// It wraps `TryFrom` trait and will panic if `try_from()` fails.
///
/// Examples:
///
/// ```
/// use primitive_fixed_point_decimal::{ConstScaleFpdec, OobScaleFpdec, fpdec};
/// type DecConst = ConstScaleFpdec<i64, 2>;
/// type DecOob = OobScaleFpdec<i64>;
///
/// let d1: DecConst = fpdec!(1.23); // 1 argument for ConstScaleFpdec
/// let d2: DecOob = fpdec!(1.23, 2); // 2 arguments for OobScaleFpdec
/// ```
#[macro_export]
macro_rules! fpdec {
    ($n:expr) => {
        primitive_fixed_point_decimal::ConstScaleFpdec::try_from($n).unwrap()
    };
    ($n:expr, $scale:expr) => {
        primitive_fixed_point_decimal::OobScaleFpdec::try_from(($n, $scale)).unwrap()
    };
}

/// Used by method `checked_mul_ratio()` only.
pub trait IntoRatioInt<T> {
    fn to_int(self) -> T;
}

/// For primitive integer types.
impl<I, T> IntoRatioInt<T> for I
where
    I: Into<T> + num_traits::PrimInt,
{
    fn to_int(self) -> T {
        self.into()
    }
}