17 unstable releases (8 breaking)
0.9.2 | Mar 2, 2023 |
---|---|
0.9.1 | Oct 10, 2022 |
0.9.0 | Sep 8, 2022 |
0.8.0 | Jun 23, 2022 |
0.2.3 | Dec 30, 2020 |
#224 in Rust patterns
1,890 downloads per month
185KB
3.5K
SLoC
fixnum
Fixed-point numbers with explicit rounding.
lib.rs
:
fixnum
Fixed-point numbers with explicit rounding.
Uses various signed integer types to store the number.
Features
Turn them on in Cargo.toml
:
i128
—i128
layout support which will be promoted to internally implementedI256
for multiplication and division.i64
—i64
layout support which will be promoted toi128
for multiplication and division.i32
—i32
layout support which will be promoted toi64
for multiplication and division.i16
—i16
layout support which will be promoted toi32
for multiplication and division.parity
—parity-scale-codec
support (Encode
andDecode
implementations).serde
— support forserde
.schemars
— support forschemars
.std
— Enabled by default.
At least one of i128
, i64
, i32
, i16
must be enabled.
Example
# #[cfg(feature = "i64")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use fixnum::{FixedPoint, typenum::U9, ops::{CheckedAdd, RoundingMul, RoundMode::*, Zero}};
/// Signed fixed point amount over 64 bits, 9 decimal places.
///
/// MAX = (2 ^ (BITS_COUNT - 1) - 1) / 10 ^ PRECISION =
/// = (2 ^ (64 - 1) - 1) / 1e9 =
/// = 9223372036.854775807 ~ 9.2e9
/// ERROR_MAX = 0.5 / (10 ^ PRECISION) =
/// = 0.5 / 1e9 =
/// = 5e-10
type Amount = FixedPoint<i64, U9>;
let a: Amount = "0.1".parse()?;
let b: Amount = "0.2".parse()?;
assert_eq!(a.cadd(b)?, "0.3".parse()?);
let expences: Amount = "0.000000001".parse()?;
// 1e-9 * (Floor) 1e-9 = 0
assert_eq!(expences.rmul(expences, Floor)?, Amount::ZERO);
// 1e-9 * (Ceil) 1e-9 = 1e-9
assert_eq!(expences.rmul(expences, Ceil)?, expences);
# Ok(()) }
# #[cfg(not(feature = "i64"))]
# fn main() {}
Available operations
Method | Example (pseudo-code) | Description |
---|---|---|
cadd |
let result: Result<FixedPoint, ArithmeticError> = a.cadd(b) |
Checked addition. Returns Err on overflow. |
csub |
let result: Result<FixedPoint, ArithmeticError> = a.csub(b) |
Checked subtraction. Returns Err on overflow. |
cmul |
let result: Result<FixedPoint, ArithmeticError> = a.cmul(b) |
Checked multiplication. Returns Err on overflow. This is multiplication without rounding, hence it's available only when at least one operand is integer. |
rmul |
let result: Result<FixedPoint, ArithmeticError> = a.rmul(b, RoundMode::Ceil) |
Checked rounding multiplication. Returns Err on overflow. Because of provided RoundMode it's possible across the FixedPoint values. |
rdiv |
let result: Result<FixedPoint, ArithmeticError> = a.rdiv(b, RoundMode::Floor) |
Checked rounding division. Returns Err on overflow. |
rsqrt |
let result: Result<FixedPoint, ArithmeticError> = a.rsqrt(RoundMode::Floor) |
Checked rounding square root. Returns Err for negative argument. |
cneg |
let result: Result<FixedPoint, ArithmeticError> = a.cneg() |
Checked negation. Returns Err on overflow (you can't negate MIN value). |
integral |
let y: {integer} = x.integral(RoundMode::Floor) |
Takes rounded integral part of the number. |
saturating_add |
let z: FixedPoint = x.saturating_add(y) |
Saturating addition |
saturating_sub |
let z: FixedPoint = x.saturating_sub(y) |
Saturating subtraction |
saturating_mul |
let z: FixedPoint = x.saturating_mul(y) |
Saturating multiplication. This is multiplication without rounding, hence it's available only when at least one operand is integer. |
saturating_rmul |
let z: FixedPoint = x.saturating_rmul(y, RoundMode::Floor) |
Saturating rounding multiplication |
Implementing wrapper types.
It's possible to restrict the domain in order to reduce chance of mistakes.
Note that convenient fixnum!
macro works with wrapper types too.
# #[cfg(feature = "i64")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use derive_more::From;
use fixnum::{impl_op, typenum::U9, FixedPoint, fixnum};
type Fp64 = FixedPoint<i64, U9>;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, From)]
struct Size(i32);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, From)]
struct Price(Fp64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, From)]
struct PriceDelta(Fp64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, From)]
struct Amount(Fp64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, From)]
struct Ratio(Fp64);
impl_op!(Size [cadd] Size = Size);
impl_op!(Size [csub] Size = Size);
impl_op!(Size [rdiv] Size = Ratio);
impl_op!(Size [cmul] Price = Amount);
impl_op!(Price [csub] Price = PriceDelta);
impl_op!(Price [cadd] PriceDelta = Price);
impl_op!(Price [rdiv] Price = Ratio);
impl_op!(Price [rmul] Ratio = Price);
impl_op!(PriceDelta [cadd] PriceDelta = PriceDelta);
impl_op!(Amount [cadd] Amount = Amount);
impl_op!(Amount [csub] Amount = Amount);
// Use it.
use fixnum::ops::*;
let size = Size(4);
let price = fixnum!(4.25, 9); // compile-time
let amount = size.cmul(price)?;
assert_eq!(amount, fixnum!(17, 9));
# Ok(()) }
# #[cfg(not(feature = "i64"))]
# fn main() {}
Dependencies
~0.8–1.5MB
~36K SLoC