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

Download history 133/week @ 2022-11-29 324/week @ 2022-12-06 284/week @ 2022-12-13 321/week @ 2022-12-20 109/week @ 2022-12-27 62/week @ 2023-01-03 89/week @ 2023-01-10 234/week @ 2023-01-17 286/week @ 2023-01-24 182/week @ 2023-01-31 321/week @ 2023-02-07 337/week @ 2023-02-14 247/week @ 2023-02-21 669/week @ 2023-02-28 679/week @ 2023-03-07 250/week @ 2023-03-14

1,890 downloads per month

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

# #[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