#fixed #decimal #numbers

no-std fixnum

Fixed-point numbers with explicit rounding

14 releases (7 breaking)

0.8.0 Jun 23, 2022
0.6.1 Dec 30, 2021
0.6.0 Jul 1, 2021
0.5.0 Mar 23, 2021
0.2.3 Dec 30, 2020

#126 in Rust patterns

Download history 171/week @ 2022-03-14 133/week @ 2022-03-21 184/week @ 2022-03-28 171/week @ 2022-04-04 130/week @ 2022-04-11 260/week @ 2022-04-18 238/week @ 2022-04-25 207/week @ 2022-05-02 266/week @ 2022-05-09 573/week @ 2022-05-16 463/week @ 2022-05-23 516/week @ 2022-05-30 473/week @ 2022-06-06 200/week @ 2022-06-13 344/week @ 2022-06-20 259/week @ 2022-06-27

1,314 downloads per month

MIT/Apache

175KB
3K SLoC

fixnum

Fixed-point numbers with explicit rounding.

Crates.io Documentation MIT licensed Build Status


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:

  • i128i128 layout support which will be promoted to internally implemented I256 for multiplication and division.
  • i64i64 layout support which will be promoted to i128 for multiplication and division.
  • i32i32 layout support which will be promoted to i64 for multiplication and division.
  • i16i16 layout support which will be promoted to i32 for multiplication and division.
  • parityparity-scale-codec support (Encode and Decode implementations).
  • serde — support for serde. Enabled by default.
  • 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.5–1.2MB
~29K SLoC