Coder Social home page Coder Social logo

mathiswellmann / fpdec.rs Goto Github PK

View Code? Open in Web Editor NEW

This project forked from mamrhein/fpdec.rs

0.0 0.0 0.0 272 KB

Fast implementation of Decimal fixed-point arithmetics

License: BSD 3-Clause "New" or "Revised" License

Shell 0.03% Rust 99.97%

fpdec.rs's Introduction

This crate provides a fast implementation of Decimal fixed-point arithmetics. It is targeted at typical business applications, dealing with numbers representing quantities, money and the like, not at scientific computations, for which the accuracy of floating point math is - in most cases - sufficient.

Objectives

  • "Exact" representation of decimal numbers (no deviation as with binary floating point numbers)
  • No hidden rounding errors (as inherent to floating point math)
  • Very fast operations (by mapping them to integer ops)
  • Range of representable decimal numbers sufficient for typical business applications

At the binary level a Decimal number is represented as a coefficient (stored as an i128 value) combined with a value specifying the number of fractional decimal digits (stored as a u8). The latter is limited to a value given by the constant MAX_N_FRAC_DIGITS = 18.

Status

Work in progess, but most of the API is stable.

Getting started

Add fpdec to your Cargo.toml:

[dependencies]
fpdec = "0.10"

Usage

A Decimal number can be created in different ways.

The easiest method is to use the procedural macro Dec:

# use fpdec::{Dec, Decimal};
let d = Dec!(-17.5);
assert_eq!(d.to_string(), "-17.5");

Alternatively you can convert an integer, a float or a string to a Decimal:

# use fpdec::Decimal;
let d = Decimal::from(297_i32);
assert_eq!(d.to_string(), "297");
# use fpdec::{Decimal, DecimalError};
# use core::convert::TryFrom;
let d = Decimal::try_from(83.25_f64)?;
assert_eq!(d.to_string(), "83.25");
# Ok::<(), DecimalError>(())
# use fpdec::{Decimal, ParseDecimalError};
# use core::str::FromStr;
let d = Decimal::from_str("38.2070")?;
assert_eq!(d.to_string(), "38.2070");
# Ok::<(), ParseDecimalError>(())

The sign of a Decimal can be inverted using the unary minus operator and a Decimal instance can be compared to other instances of type Decimal or all basic types of integers (besides u128):

# use fpdec::{Dec, Decimal};
let x = Dec!(129.24);
let y = -x;
assert_eq!(y.to_string(), "-129.24");
assert!(-129_i64 > y);
let z = -y;
assert_eq!(x, z);
let z = Dec!(0.00097);
assert!(x > z);
assert!(y <= z);
assert!(z != 7_u32);
assert!(7_u32 == Dec!(7.00));

Decimal supports all five binary numerical operators +, -, *, /, and %, with two Decimals or with a Decimal and a basic integer (besides u128):

# use fpdec::{Dec, Decimal};
let x = Dec!(17.5);
let y = Dec!(6.40);
let z = x + y;
assert_eq!(z.to_string(), "23.90");
let z = x - y;
assert_eq!(z.to_string(), "11.10");
let z = x * y;
assert_eq!(z.to_string(), "112.000");
let z = x / y;
assert_eq!(z.to_string(), "2.734375");
let z = x % y;
assert_eq!(z.to_string(), "4.70");
# use fpdec::{Dec, Decimal};
let x = Dec!(17.5);
let y = -5_i64;
let z = x + y;
assert_eq!(z.to_string(), "12.5");
let z = x - y;
assert_eq!(z.to_string(), "22.5");
let z = y * x;
assert_eq!(z.to_string(), "-87.5");
let z = x / y;
assert_eq!(z.to_string(), "-3.5");
let z = x % y;
assert_eq!(z.to_string(), "2.5");

The results of Multiplication or Division are not exact in any case. If the number of fractional decimal digits of the exact result would exceed MAX_N_FRAC_DIGITS fractional decimal digits, the result given is rounded to fit this limit.

# use fpdec::{Dec, Decimal};
let x = Dec!(1e-10);
let y = Dec!(75e-9);
let z = x * y;
assert_eq!(z.to_string(), "0.000000000000000008");
let x = Dec!(1.);
let y = Dec!(3.);
let z = x / y;
assert_eq!(z.to_string(), "0.333333333333333333");

All these binary numeric operators panic if the result is not representable as a Decimal according to the constraints stated above. In addition, there are functions implementing "checked" variants of the operators which return Option::None instead of panicking.

For Multiplication and Division there are also functions which return a result rounded to a given number of fractional digits:

# use fpdec::{Dec, Decimal, DivRounded, MulRounded};
let x = Dec!(17.5);
let y = Dec!(6.47);
let z: Decimal = x.mul_rounded(y, 1);
assert_eq!(z.to_string(), "113.2");
let z: Decimal = x.div_rounded(y, 3);
assert_eq!(z.to_string(), "2.705");

A Decimal value can be converted into a float, maybe rounded to the nearest value representable by the target type:

# use fpdec::{Dec, Decimal};
let d = Dec!(-33820900478.195);
let f = f64::from(d);
assert_eq!(f, -33820900478.19499969482421875_f64);
let f = f32::from(Dec!(0.6));
assert_eq!(f, 0.60000002384185791015625_f32);

Converting a Decimal value to a primitive int is more intricate. It is only supported by try_from / try_into and only giving a value of the target type, if the given value represents an integral value fitting the range of values of the target type.

# use fpdec::{Dec, Decimal, TryFromDecimalError};
let d = Dec!(3.7);
let res = i32::try_from(d);
assert!(res.is_err());
assert_eq!(res.unwrap_err(), TryFromDecimalError::NotAnIntValue);
let d = Decimal::MAX;
let res = i128::try_from(d);
assert_eq!(res.unwrap(), i128::MAX);
let res = i64::try_from(d);
assert!(res.is_err());
assert_eq!(res.unwrap_err(), TryFromDecimalError::ValueOutOfRange);

Crate features

By default, only the feature std is enabled.

Ecosystem

  • std - When enabled, this will cause fpdec to use the standard library, so that conversion to string, formatting and printing are available. When disabled, the use of crate alloc together with a system-specific allocator is needed to use that functionality.

  • packed - When enabled, the struct Decimal is marked with #[repr(packed)].

Optional dependencies

  • num-traits - When enabled, the trait num-traits::Num is implemented for Decimal.

  • serde-as-str - When enabled, support for serde is enabled. This allows Decimal instances to be serialzed as strings and to be deserialized from strings via serde.

  • rkyv - When enabled, support for rkyv is enabled. This allows Decimal instances to be zero-copy serialized and deserialized via rkyv archives.

fpdec.rs's People

Contributors

mamrhein avatar roms1383 avatar den-mentiei avatar mathiswellmann avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.