| Crates.io | fixdec |
| lib.rs | fixdec |
| version | 0.1.0 |
| created_at | 2025-11-16 20:30:49.528777+00 |
| updated_at | 2025-11-16 20:30:49.528777+00 |
| description | High-performance fixed-point decimal types for financial calculations and cryptocurrency |
| homepage | |
| repository | https://github.com/Abso1ut3Zer0/fixdec |
| max_upload_size | |
| id | 1935880 |
| size | 349,325 |
High-performance fixed-point decimal arithmetic for financial calculations and cryptocurrency.
fixdec provides two blazingly fast decimal types with fixed precision, optimized for performance-critical applications where the precision requirements are known at compile time. If you need configurable precision at runtime, use rust_decimal. If you need maximum speed with fixed precision, use fixdec.
✅ Use fixdec when:
no_std support for embedded or WASM❌ Don't use fixdec when:
fixdec is designed for speed. Here's how it compares to rust_decimal:
| Operation | D64 | rust_decimal | Speedup |
|---|---|---|---|
| Addition | 0.62 ns | 7.51 ns | 12x faster |
| Subtraction | 0.60 ns | 7.53 ns | 12.5x faster |
| Multiplication | 1.24 ns | 7.57 ns | 6x faster |
| Division | 3.70 ns | 21.29 ns | 5.7x faster |
| Square root | 102.87 ns | 750.89 ns | 7.3x faster |
| Power (powi) | 5.15 ns | 42.67 ns | 8.3x faster |
| Bincode serialize | 6.0 ns | 62.9 ns | 10.5x faster |
Benchmarks run on [your system specs]. See benches/ for details.
D64 - Traditional FinanceD96 - CryptocurrencyAdd to your Cargo.toml:
[dependencies]
fixdec = "0.1"
use fixdec::D64;
use core::str::FromStr;
// Create from strings
let price = D64::from_str("1234.56")?;
let quantity = D64::from_i32(100);
// Fast arithmetic
let total = price * quantity;
assert_eq!(total.to_string(), "123456");
// Checked arithmetic
let result = price.checked_mul(quantity).ok_or("overflow")?;
// Financial operations
let fee = total.percent_of(D64::from_str("0.1")?)?; // 0.1% fee
use fixdec::D96;
use core::str::FromStr;
// High precision for crypto
let eth_price = D96::from_str("2500.123456789012")?;
let amount = D96::from_str("0.5")?;
let total_value = eth_price * amount;
// Built-in crypto constants
let gas_price = D96::from_i64(50) * D96::GWEI; // 50 gwei
let tx_value = D96::from_str("0.00000001")?; // 1 satoshi equivalent
no_std compatible: Works in embedded systems and WebAssemblyalloc: For Vec and String supportstd: For Error trait and standard library features[dependencies]
fixdec = { version = "0.1", features = ["serde"] }
| Feature | Description |
|---|---|
default |
No additional features (pure no_std) |
alloc |
Enable Vec and String support |
std |
Enable standard library and Error trait |
serde |
Enable Serde serialization (requires alloc) |
full |
Enable all features |
// From integers
D64::from_i32(42) // Always succeeds
D64::from_i64(1000000)? // Checked, may overflow
D64::from_u64(1000000)? // Checked
// From strings (exact)
D64::from_str("123.45")? // Errors if > 8 decimals
D64::from_str_lossy("123.456789123")? // Rounds to 8 decimals
// From floats (lossy)
D64::from_f64(123.45)? // May lose precision
// From raw scaled values (advanced)
D64::from_raw(12345000000) // 123.45 in raw form
// From mantissa and scale (rust_decimal compatibility)
D64::with_scale(12345, 2)? // 123.45 (mantissa=12345, scale=2)
// Standard operators (panic on overflow)
let z = x + y;
let z = x - y;
let z = x * y;
let z = x / y;
// Checked (returns Option)
x.checked_add(y)?
x.checked_sub(y)?
x.checked_mul(y)?
x.checked_div(y)?
// Saturating (clamps to min/max)
x.saturating_add(y)
x.saturating_sub(y)
x.saturating_mul(y)
x.saturating_div(y)
// Wrapping (wraps on overflow)
x.wrapping_add(y)
x.wrapping_sub(y)
x.wrapping_mul(y)
x.wrapping_div(y)
// Fast integer multiplication (quantity * price)
price.mul_i64(quantity)?
// Fused multiply-add (one rounding step)
x.mul_add(y, z)? // (x * y) + z
let x = D64::from_str("123.456789")?;
x.floor() // 123.00000000
x.ceil() // 124.00000000
x.round() // 123.00000000 (banker's rounding)
x.round_dp(2) // 123.46000000 (round to 2 decimals)
x.trunc() // 123.00000000 (truncate)
x.fract() // 0.45678900 (fractional part)
// Basis points (1 bp = 0.0001)
let rate = D64::from_basis_points(50)?; // 0.005 (50 bps)
let bps = rate.to_basis_points(); // 50
// Percentage calculations
let tax = price.percent_of(D64::from_str("8.5")?)?; // 8.5% of price
let with_markup = price.add_percent(D64::from_str("10")?)?; // price * 1.10
// Financial constants
D64::BASIS_POINT // 0.0001 (1 bp)
D64::HALF_BASIS_POINT // 0.00005
D64::THIRTY_SECOND // 0.03125 (US Treasury bond tick)
D64::SIXTY_FOURTH // 0.015625
D64::CENT // 0.01 (1 cent)
D64::PERCENT // 0.01 (1%)
// Square root
let sqrt = x.sqrt()?;
// Integer powers
let squared = x.powi(2)?;
let cubed = x.powi(3)?;
// Reciprocal
let recip = x.recip()?; // 1/x
// Absolute value
let abs = x.abs();
// Sign
let sign = x.signum(); // -1, 0, or 1
With the serde feature enabled:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Trade {
price: D64,
quantity: D64,
}
// JSON: uses string representation for precision
let json = serde_json::to_string(&trade)?;
// {"price":"1234.56","quantity":"100.00000000"}
// Bincode: uses raw i64 (extremely fast)
let bytes = bincode::serialize(&trade)?;
// Just 16 bytes (8 bytes per D64)
D64::ZERO // 0
D64::ONE // 1.0
D64::TEN // 10.0
D64::HUNDRED // 100.0
D64::THOUSAND // 1000.0
// Currency
D64::CENT // 0.01
D64::MIL // 0.001
// Basis points
D64::BASIS_POINT // 0.0001
D64::HALF_BASIS_POINT // 0.00005
// Bond pricing
D64::THIRTY_SECOND // 1/32
D64::SIXTY_FOURTH // 1/64
// Legacy equity fractions
D64::EIGHTH // 1/8
D64::SIXTEENTH // 1/16
// Cryptocurrency
D96::SATOSHI // 0.00000001 (Bitcoin)
D96::GWEI // 0.000000001 (Ethereum gas unit)
D96::MICRO_GWEI // 0.000000000001 (minimum precision)
D96::KILO_WEI // 0.000000000001 (1000 wei)
fixdec works in no_std environments by default:
#![no_std]
use fixdec::D64;
// All core operations work without std
let x = D64::from_i32(42);
let y = D64::from_i32(10);
let z = x / y;
// String formatting requires alloc
#[cfg(feature = "alloc")]
extern crate alloc;
use fixdec::D64;
struct Position {
symbol: &'static str,
quantity: i64,
entry_price: D64,
current_price: D64,
}
fn calculate_pnl(position: &Position) -> D64 {
let entry_value = position.entry_price.mul_i64(position.quantity).unwrap();
let current_value = position.current_price.mul_i64(position.quantity).unwrap();
current_value - entry_value
}
let pos = Position {
symbol: "AAPL",
quantity: 1000,
entry_price: D64::from_str("150.25")?,
current_price: D64::from_str("155.75")?,
};
let pnl = calculate_pnl(&pos);
assert_eq!(pnl.to_string(), "5500"); // $5,500 profit
use fixdec::D96;
fn calculate_swap_output(
amount_in: D96,
reserve_in: D96,
reserve_out: D96,
fee_bps: i64, // e.g., 30 for 0.3%
) -> Option<D96> {
let fee_multiplier = D96::from_basis_points(10000 - fee_bps)?;
let amount_in_with_fee = amount_in.checked_mul(fee_multiplier)?
.checked_div(D96::from_i32(10000))?;
let numerator = amount_in_with_fee.checked_mul(reserve_out)?;
let denominator = reserve_in.checked_add(amount_in_with_fee)?;
numerator.checked_div(denominator)
}
let amount_out = calculate_swap_output(
D96::from_str("1.0")?, // 1 ETH in
D96::from_str("1000")?, // Reserve: 1000 ETH
D96::from_str("2000000")?, // Reserve: 2M USDC
30, // 0.3% fee
)?;
use fixdec::D64;
// US Treasury bonds are quoted in 32nds
// e.g., "99-16" means 99 + 16/32 = 99.5
fn parse_bond_price(whole: i64, thirty_seconds: i64) -> D64 {
let whole_part = D64::from_i64(whole);
let fraction = D64::THIRTY_SECOND.mul_i64(thirty_seconds).unwrap();
whole_part + fraction
}
let price = parse_bond_price(99, 16);
assert_eq!(price.to_string(), "99.5");
| Feature | fixdec | rust_decimal |
|---|---|---|
| Precision | Fixed (8 or 12 decimals) | Configurable (0-28 decimals) |
| Performance | 6-12x faster | Slower due to flexibility |
| Use case | Performance-critical with known precision | General purpose, configurable precision |
| no_std | ✅ Full support | ✅ Full support |
| Serialization | ✅ Optimized for binary | ✅ General purpose |
| API similarity | High (easy migration) | - |
fixdec is built for speed when you know your precision requirements. rust_decimal is built for flexibility when you need configurable precision.
checked, saturating, and wrapping variantsproptest verify correctness against baseline implementationsContributions are welcome! Areas of interest:
Licensed under either of:
at your option.
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.