#fixed #decimal #numbers

no-std fixnum

Fixed-point numbers with explicit rounding

12 releases

0.6.1 Dec 30, 2021
0.6.0 Jul 1, 2021
0.5.1 May 28, 2021
0.5.0 Mar 23, 2021
0.2.3 Dec 30, 2020

#175 in Rust patterns

Download history 558/week @ 2021-09-27 537/week @ 2021-10-04 702/week @ 2021-10-11 808/week @ 2021-10-18 355/week @ 2021-10-25 230/week @ 2021-11-01 374/week @ 2021-11-08 219/week @ 2021-11-15 535/week @ 2021-11-22 823/week @ 2021-11-29 240/week @ 2021-12-06 116/week @ 2021-12-13 103/week @ 2021-12-20 140/week @ 2021-12-27 53/week @ 2022-01-03 176/week @ 2022-01-10

496 downloads per month

MIT/Apache

175KB
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. The following are available by default:

  • i16 — promotes to i32 for multiplication and division,
  • i32 — promotes to i64 (for mul, div),
  • i64 — promotes to i128 (for mul, div).

Features

Turn them on in Cargo.toml:

  • i128i128 layout support which will be promoted to internally implemented I256 for multiplication and division.
  • parityparity-scale-codec support (Encode and Decode implementations).

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

# fn main() -> Result<(), Box<dyn std::error::Error>> {
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(()) }

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.
# fn main() -> Result<(), Box<dyn std::error::Error>> {
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(()) }

Dependencies

~0.6–1.3MB
~31K SLoC