Why OptionStratLib Uses rust_decimal Instead of f64 for Financial Precision
OptionStratLib uses rust_decimal instead of native f64 to eliminate binary floating-point rounding errors in monetary calculations, accepting a modest 2–4× performance overhead to guarantee exact decimal representation required for options pricing, Greeks calculations, and regulatory compliance.
OptionStratLib is a financial-oriented Rust library where exact monetary values are central to every operation—from Black-Scholes pricing to volatility surface modeling. Unlike scientific computing, financial mathematics demands deterministic base-10 arithmetic to prevent subtle rounding errors that accumulate across risk metrics and strategy P&L calculations. The library therefore implements rust_decimal as its core numeric type throughout the codebase, explicitly documenting this precision-over-performance trade-off in the core option model implementation.
The Precision Problem with f64 in Financial Software
Binary floating-point types like f64 cannot represent most decimal fractions exactly, creating unacceptable drift in financial calculations where "exact to the cent" accuracy is mandatory.
Binary Representation Errors
The f64 type stores numbers as binary fractions, meaning values like 0.1 become infinite repeating sequences that round to 0.10000000000000000555. While insignificant in isolation, these micro-errors accumulate through iterative operations—such as Monte Carlo simulations or daily P&L aggregations—causing pricing drift and incorrect Greeks. In src/model/option.rs at line 345, the library explicitly notes this design constraint, emphasizing that financial audit standards require deterministic arithmetic that native floating-point cannot guarantee.
Accumulation Risk in Iterative Calculations
Options pricing involves repeated multiplications, exponentiations, and cumulative distribution calculations. With f64, each operation potentially introduces representational noise that compounds across thousands of iterations. The rust_decimal crate stores values as scaled 96-bit integers with up to 28–38 decimal digits of precision, ensuring that inputs like 0.01 or 0.001 remain exact throughout the calculation chain.
Why OptionStratLib Chooses rust_decimal
The library’s selection of rust_decimal addresses four critical financial computing requirements that f64 cannot satisfy.
Exact Decimal Representation
rust_decimal implements base-10 fixed-scale arithmetic, guaranteeing that currency values maintain their exact representation regardless of operation count. This is essential for USD (2 decimal places), FX pairs (often 4–6 decimals), and implied volatility surfaces where basis-point precision determines profitability. The workspace dependency in Cargo.toml at line 57 declares rust_decimal with the maths and serde features enabled, providing both mathematical operations and exact serialization support.
Built-in Rounding and Compliance
Financial regulations require specific rounding modes—ROUND_HALF_EVEN, ROUND_UP, ROUND_DOWN—for audit trails and reporting. rust_decimal implements these modes natively, whereas f64 requires manual rounding logic that risks implementation errors. This deterministic behavior ensures that pricing outputs match regulatory expectations exactly, byte-for-byte.
Safe Error Handling
Unlike f64, which silently overflows to infinity or underflows to zero, rust_decimal returns Option or Result types on conversion failures. In src/utils/others.rs at line 96, the random_decimal function demonstrates this pattern:
Decimal::from_f64(value).ok_or(…)
This explicit error handling prevents silent data corruption when integrating external f64 data sources into the decimal-based calculation pipeline.
Serialization Integrity
The serde feature preserves exact textual representation during CSV/JSON serialization, preventing scientific notation or trailing-zero truncation that confuses downstream accounting systems. This ensures that a value serialized as 123.4500 deserializes identically, maintaining audit trail integrity.
Performance Implications of rust_decimal vs f64
While rust_decimal provides correctness guarantees, it incurs measurable overhead compared to hardware-accelerated f64 operations.
Arithmetic Operation Overhead
rust_decimal performs scaled integer arithmetic with overflow checks on every operation, plus conversion overhead when interfacing with primitive types. According to benchmarks in benches/model/positive.rs, the Positive wrapper type—which internally holds a Decimal—exhibits the following relative costs:
- Creation from
f64: ~2–3× slower due to validation and conversion logic - Basic arithmetic (
+,-,*,/): ~2–4× slower due to scaled integer ops and overflow checks - Mathematical functions (
sqrt,ln,exp): Similar factor, dominated byrust_decimal’s internal algorithms rather than hardware FPU instructions - Comparisons (
==,>): Slightly slower but negligible in typical strategy loops
Why the Trade-off is Acceptable
The heavy computational load in options analysis—Monte Carlo simulations, volatility surface interpolations, and strategy backtests—is dominated by algorithmic complexity and memory access patterns rather than raw arithmetic throughput. The 2–4× penalty on individual operations becomes negligible when amortized across portfolio-level calculations. As implemented in joaquinbejar/optionstratlib, the core library maintains the decimal guarantee for all public APIs, prioritizing correctness over raw speed.
Users requiring ultra-high-performance lattice pricing for specific modules can compile custom builds substituting Decimal with f64, though this sacrifices the exactness guarantees that define the library’s financial reliability.
Implementation Examples in OptionStratLib
The following patterns demonstrate how rust_decimal integrates into the library’s pricing and risk workflows.
Creating Decimal-Based Prices
Use the dec! macro from rust_decimal_macros to ensure compile-time exactness:
use rust_decimal_macros::dec;
use positive::Positive;
// Compile-time exact decimal literal
let strike: Positive = dec!(150.00).try_into().expect("valid decimal");
// Runtime conversion from f64 (may round)
let market_price: Positive = Positive::new(155.37_f64);
The dec! macro expands to a Decimal literal, preserving the exact decimal representation without binary intermediate steps.
Pricing Options with Decimal Precision
The Options::calculate_price_black_scholes method operates entirely on Decimal values internally, as seen in src/volatility/utils.rs at line 15 where Decimal drives the Newton-Raphson solver for implied volatility:
use optionstratlib::{Options, OptionStyle, OptionType, Side, ExpirationDate};
use positive::pos_or_panic;
use rust_decimal_macros::dec;
let opt = Options::new(
OptionType::European,
Side::Long,
"AAPL".to_string(),
pos_or_panic!(150.0), // strike: Decimal-backed Positive
ExpirationDate::Days(pos_or_panic!(30.0)),
pos_or_panic!(0.25), // implied vol
Positive::ONE, // quantity
pos_or_panic!(155.0), // underlying
dec!(0.05), // risk-free rate: exact decimal
OptionStyle::Call,
pos_or_panic!(0.02), // dividend yield
None,
);
let price = opt.calculate_price_black_scholes().unwrap();
// price is Decimal: exact to the cent or finer depending on scale
All internal calculations—including logarithms, exponentiations, and square roots—use rust_decimal mathematical implementations to prevent cumulative rounding errors in the Black-Scholes partial differential equation.
Handling Conversion Errors Safely
When interfacing with external systems providing f64 data, explicit error handling ensures precision integrity:
use rust_decimal::Decimal;
fn safe_decimal_from_f64(input: f64) -> Result<Decimal, String> {
Decimal::from_f64(input)
.ok_or_else(|| format!("Cannot represent {} as exact decimal", input))
}
This pattern appears throughout the utilities module to prevent silent precision loss when ingesting market data.
Key Source Files and Architecture
| File | Purpose |
|---|---|
Cargo.toml (line 57) |
Declares rust_decimal workspace dependency with maths and serde features |
src/model/option.rs (line 345) |
Documents the precision vs. performance trade-off in the core Option struct |
src/volatility/utils.rs (line 15) |
Implements Newton-Raphson and GARCH calculations using Decimal exclusively |
src/utils/others.rs (line 96) |
Demonstrates safe f64 to Decimal conversion with error handling |
benches/model/positive.rs |
Performance benchmarks measuring the Positive wrapper overhead |
src/curves/types.rs, src/surfaces/types.rs, src/greeks/utils.rs |
Consistent Decimal usage across curves, volatility surfaces, and Greeks calculations |
Summary
- OptionStratLib prioritizes
rust_decimaloverf64to guarantee exact monetary representation required for financial audit compliance and deterministic options pricing. - Binary floating-point introduces unacceptable rounding errors in iterative calculations, while
rust_decimalprovides base-10 exactness with 28–38 digits of precision. - Performance overhead is 2–4× slower than native
f64for arithmetic operations, an acceptable trade-off for correctness in portfolio-level risk calculations. - Explicit error handling via
Option/Resulttypes prevents silent overflow/underflow thatf64would ignore. - Built-in serialization support preserves exact decimal representation across JSON/CSV boundaries without scientific notation artifacts.
Frequently Asked Questions
Can I use f64 with OptionStratLib for faster performance?
The public API intentionally requires Decimal or the Positive wrapper type for all monetary fields to maintain correctness guarantees. While you could fork the repository and substitute f64 for specific high-performance modules, you would sacrifice the exact cent-level precision required for regulatory compliance and risk calculation accuracy.
How much slower is rust_decimal compared to f64 in benchmarks?
According to the library’s benchmarks in benches/model/positive.rs, rust_decimal operations through the Positive wrapper are approximately 2–4× slower than raw f64 for basic arithmetic and mathematical functions. Creation from f64 incurs a ~2–3× penalty due to validation and conversion overhead.
Does rust_decimal handle all mathematical operations needed for options pricing?
Yes. The Cargo.toml enables the maths feature for rust_decimal, providing logarithms, exponentiation, square roots, and power functions required for Black-Scholes pricing, Greeks calculations, and volatility modeling (EWMA, GARCH) as implemented in src/volatility/utils.rs.
Why not use fixed-point integers instead of rust_decimal?
Fixed-point integers require compile-time scale determination (e.g., 4 decimal places), which lacks flexibility for cross-asset calculations where FX might need 6 decimals while equities need 2. rust_decimal provides runtime scale management and ergonomic mathematical operations while maintaining exactness, offering the flexibility of floating-point with the precision of integers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →