Crate fixnum

source ·
Expand description

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

MethodExample (pseudo-code)Description
caddlet result: Result<FixedPoint, ArithmeticError> = a.cadd(b)Checked addition. Returns Err on overflow.
csublet result: Result<FixedPoint, ArithmeticError> = a.csub(b)Checked subtraction. Returns Err on overflow.
cmullet 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.
rmullet 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.
rdivlet result: Result<FixedPoint, ArithmeticError> = a.rdiv(b, RoundMode::Floor)Checked rounding division. Returns Err on overflow.
rsqrtlet result: Result<FixedPoint, ArithmeticError> = a.rsqrt(RoundMode::Floor)Checked rounding square root. Returns Err for negative argument.
cneglet result: Result<FixedPoint, ArithmeticError> = a.cneg()Checked negation. Returns Err on overflow (you can’t negate MIN value).
integrallet y: {integer} = x.integral(RoundMode::Floor)Takes rounded integral part of the number.
saturating_addlet z: FixedPoint = x.saturating_add(y)Saturating addition
saturating_sublet z: FixedPoint = x.saturating_sub(y)Saturating subtraction
saturating_mullet 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_rmullet 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));

Re-exports

Modules

  • Contains traits for checked and rounding operations.
  • serdeserde
    A module that contains instances of Serialize and Deserialize for FixedPoint. Also contains submodule that can be provided to serde(with) in order to change the implementation.

Macros

  • Macro to create fixed-point “literals”. Contains .into() call inside so you can use it with your From<FixedPoint> wrapper types.
  • Macro to create fixed-point const “literals”.
  • Defines an operation for some wrapper. See top-level documentation.

Structs

  • Represents errors during conversions.
  • FixedPointi128 or i64 or i32 or i16
    Abstraction over fixed point numbers of arbitrary (but only compile-time specified) size and precision.

Enums

Traits

  • The number of digits in the fractional part.