Crates.io | fastnum |
lib.rs | fastnum |
version | 0.0.5 |
source | src |
created_at | 2024-10-21 15:16:38.276713 |
updated_at | 2024-11-06 11:20:10.834197 |
description | Fast numbers library |
homepage | https://github.com/neogenie/fastnum |
repository | https://github.com/neogenie/fastnum |
max_upload_size | |
id | 1417628 |
size | 258,859 |
Fixed-size signed and unsigned integers and arbitrary precision decimal numbers implemented in pure Rust. Suitable for financial, crypto and any other fixed-precision calculations.
This crate is inspired by num_bigint and bigdecimal - an amazing crates that allows you to store big integers and arbitrary precision fixed-point decimal numbers almost any precision.
BigInt internally uses a Vec
of decimal digits
the size of which is theoretically limited only by the usize
max value or memory capacity.
Under the hood BigDecimal uses a BigInt object, paired with a 64-bit integer which determines the position of the decimal point. Therefore, the precision is not actually arbitrary, but limited to 2 63 decimal places.
Despite the seemingly undeniable advantages at first glance, this approach also has a number of fundamental disadvantages:
0
or 1
.Because most practical problems requiring the use of fixed-point numbers do not require so much
limit on the number of digits, such as usize
, but as a rule it is limited:
Unit | Precision | Decimal digits |
---|---|---|
United States Dollar (USD) | 0.01 | 2 |
United States Dollar, stock (USD) | 0.0001 | 4 |
Bitcoin (BTC) | 10-8 | 8 |
Ethereum (ETH) | 10-18 | 18 |
Then most real numbers for financial and other systems requiring accuracy can use 256-bit or even 128-bit integer to store decimal digits.
So In this library, a different approach was chosen.
For big integers this crate provides integer types of arbitrary fixed size which behave exactly like Rust's internal
primitive integer types (u8
, i8
, u16
, i16
, etc.):
Unit | Bits | Representation | Signed | Min | Max | Helper Macro |
---|---|---|---|---|---|---|
I128 |
128 | 2 x u64 |
+ | -2127 | 2127-1 | int128!(1) |
U128 |
128 | 2 x u64 |
0 | 2128 | uint128!(1) |
|
I256 |
256 | 4 x u64 |
+ | -2255 | 2255-1 | int256!(1) |
U256 |
256 | 4 x u64 |
0 | 2256 | uint256!(1) |
|
I512 |
512 | 8 x u64 |
+ | -2511 | 2511-1 | int512!(1) |
U512 |
512 | 8 x u64 |
0 | 2512 | uint512!(1) |
|
I1024 |
1024 | 16 x u64 |
+ | -21023 | 21023-1 | int1024!(1) |
U1024 |
1024 | 16 x u64 |
0 | 21024 | uint1024!(1) |
|
I2048 |
2048 | 32 x u64 |
+ | -22047 | 22047-1 | int2048!(1) |
U2048 |
2048 | 32 x u64 |
0 | 22048 | uint2048!(1) |
|
I4096 |
4096 | 64 x u64 |
+ | -24095 | 24095-1 | int4096!(1) |
U4096 |
4096 | 64 x u64 |
0 | 24096 | uint4096!(1) |
|
I8192 |
8192 | 128 x u64 |
+ | -28191 | 28191-1 | int8192!(1) |
U8192 |
8192 | 128 x u64 |
0 | 28192 | uint8192!(1) |
Nearly all methods defined on Rust's signed and unsigned primitive integers are defined fastnum
's signed and unsigned
integers.
Under the hood bnum is currently used as the backend as most meeting the
requirements.
Subsequently, the implementation can be replaced in favor of its own implementation, which enables SIMD
.
Unsigned integers are stored as an array of digits (primitive unsigned integers) of length N
. This means all
fastnum
integers can be stored on the stack, as they are fixed size. Signed integers are simply stored as an unsigned
integer in two's complement.
fastnum
provides a several decimal numbers suitable for financial calculations that require significant
integral and fractional digits with no round-off errors.
Decimal type | Integer part | Bits | Memory representation | Signed | Max significant_digits | Helper macro |
---|---|---|---|---|---|---|
D128 |
U128 |
128 | 2 x u64 + i64 + i64 |
+ | 2128 | dec128!(0.1) |
UD128 |
U128 |
128 | 2 x u64 + i64 |
2128 | udec128!(0.1) |
|
D256 |
U256 |
256 | 4 x u64 + i64 + i64 |
+ | 2256 | dec256!(0.1) |
UD256 |
U256 |
256 | 4 x u64 + i64 |
2256 | udec256!(0.1) |
|
D512 |
U512 |
512 | 8 x u64 + i64 + i64 |
+ | 2512 | dec512!(0.1) |
UD512 |
U512 |
512 | 8 x u64 + i64 |
2512 | udec512!(0.1) |
Under the hood any [D|UD]N
decimal type consists of a N-bit big unsigned integer, paired with a 64-bit signed integer
scaling factor which determines the position of the decimal point and sign (for signed types only). Therefore, the
precision is not actually arbitrary, but limited to 263 decimal places. Because of this representation,
trailing zeros are preserved and may be exposed when in string form. These can be truncated using the normalize or
round_dp functions.
Thus, fixed-point numbers are trivially copyable and do not require any dynamic allocation. This allows you to get additional performance gains by eliminating not only dynamic allocation, like such, but also will get rid of one indirect addressing, which improves cache-friendliness and reduces the CPU load.
fastnum
numerics as fast as native types, well almost :).fastnum
numerics are trivially copyable (both integer and decimal, ether signed
and unsigned) and can be stored on the stack, as they are fixed size.from_*
methods on fastnum
integers
and decimals are const
, which allows parsing of integers and numerics from string slices and floats at compile time.
Additionally, the string to be parsed does not have to be a literal: it could, for example, be obtained via
include_str!
, or
env!
.fastnum
does not depend on any other crates by default. Support for crates such
as rand
and serde
can be enabled with
crate features.no-std
compatible: fastnum
can be used in no_std
environments.const
evaluation: nearly all methods defined on fastnum
integers and decimals are const
, which allows
complex compile-time calculations and checks.To install and use fastnum
, simply add the following line to your Cargo.toml
file in the [dependencies]
section:
fastnum = "0.0.3"
Or, to enable various fastnum
features as well, add for example this line instead:
fastnum = { version = "0.0.3", features = ["serde"] } # enables the "serde" feature
use fastnum::{udec256, UD256};
fn main() {
const ZERO: UD256 = udec256!(0);
const ONE: UD256 = udec256!(1.0);
let a = udec256!(12345);
println!("a = {a}");
}
The serde
feature enables serialization and deserialization of fastnum
decimals via the
serde
crate. More details about serialization and deserialization you can found
in
The diesel
feature enables serialization and deserialization of fastnum
decimals for
diesel
crate.
The sqlx
feature enables serialization and deserialization of fastnum
decimals for
sqlx
crate.
The utoipa
feature enables support of fastnum
decimals for autogenerated OpenAPI documentation via the
utoipa
crate.
fastnum
is blazing fast. As much as possible given the overhead of arbitrary precision support. It x10 faster than
bigdecimal and x1.1 - x4 slower than native floating point f64
Rust type.
This crate is tested with the rstest
crate as well as with specific edge
cases.
The current Minimum Supported Rust Version (MSRV) is 1.82.0
.
If a method is not documented explicitly, it will have a link to the equivalent method defined on primitive Rust integers (since the methods have the same functionality).
**NB: fastnum
is currently pre-1.0.0
. As per the Semantic Versioning guidelines,
the
public API may contain breaking changes while it is in this stage. However, as the API is designed to be as similar as
possible to the API of Rust's primitive integers, it is unlikely that there will be a large number of breaking changes.
**
You can set a few default parameters at compile-time via environment variables:
Environment Variable | Default |
---|---|
RUST_FASTNUM_DEFAULT_PRECISION |
100 |
RUST_FASTNUM_DEFAULT_ROUNDING_MODE |
HalfEven |
RUST_FASTNUM_FMT_EXPONENTIAL_LOWER_THRESHOLD |
5 |
RUST_FASTNUM_FMT_EXPONENTIAL_UPPER_THRESHOLD |
15 |
RUST_FASTNUM_FMT_MAX_INTEGER_PADDING |
1000 |
RUST_FASTNUM_DEFAULT_SERDE_DESERIALIZE_MODE |
Strict |
Examine build.rs for how those are converted to constants in the code (if interested).
There are several areas for further work:
This code is dual-licensed under the permissive MIT & Apache 2.0 licenses.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.