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.
-
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 addBalancetype byPricetype. -
The
*and/operations accept operand with different types and scales, and allow the result's scale specified. Certainly we need to multiply betweenBalancetype andPricetype. -
Supports 2 ways to specify the scale: const and out-of-band. See the 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. -
Supports serde traits integration (
Serialize/Deserialize) by optionalserdefeature flag. -
no-stdandno-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 ;
type Balance = ; // 2 is enough for all currencies
type Price = ; // 6 is enough for all markets
let usd: Balance = fpdec!;
let price: Price = fpdec!;
let jpy: Balance = usd * price;
assert_eq!;
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 ;
type Balance = ; // no global scale set
type Price = ; // no global scale set
// each market has its own scale configuration
// let's take BTC/USDT market as example
let btc_usdt = Market ;
// we need tell the scale to `fpdec!`
let btc: Balance = fpdec!;
let price: Price = fpdec!;
// 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.unwrap;
assert_eq!;
Obviously it's verbose to use, but offers greater flexibility.
In summary,
- if you know the scale (decimal precision) at compile time, choose [
ConstScaleFpdec]; - if you know it at runtime, choose [
OobScaleFpdec]; - if you have no idea about it (maybe because the scale is variable rather
than fixed, e.g. in a general-purpose decimal math library), you need a
floating-point decimal crate, such as
bigdecimalorrust_decimal.
You can also use these two types in combination.
For example,
use OobScaleFpdec as Balance and ConstScaleFpdec as FeeRate.
License
MIT