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

#842 in Rust patterns

Download history 53/week @ 2024-03-13 24/week @ 2024-03-20 46/week @ 2024-03-27 142/week @ 2024-04-03 60/week @ 2024-04-10 270/week @ 2024-04-17 42/week @ 2024-04-24 22/week @ 2024-05-01 11/week @ 2024-05-08 30/week @ 2024-05-15 28/week @ 2024-05-22 43/week @ 2024-05-29 57/week @ 2024-06-05 27/week @ 2024-06-12 89/week @ 2024-06-19 50/week @ 2024-06-26

250 downloads per month
Used in fixnum-approx-eq

MIT/Apache

185KB
3.5K 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.
  • schemars — support for schemars.
  • std — Enabled by default.

At least one of i128, i64, i32, i16 must be enabled.

Example

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);

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.

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));

Dependencies

~0.5–1.2MB
~29K SLoC