All articles
Smart Contracts·November 22, 2025·5 min read

Best Rust Libraries for On-Chain Math in Solana Trading Programs

Floating-point is banned on-chain, so safe fixed-point arithmetic is essential for pricing, fee calculation, and PnL settlement. We benchmark fixed, uint, and spl-math against each other for precision and CU cost in real trading workloads.

Solana's BPF runtime rejects floating-point instructions at deploy time — f64 math will fail your transaction before it touches the chain. Every price calculation, fee deduction, and PnL settlement in a trading program has to be expressed in integer arithmetic, which means choosing the right fixed-point library matters as much as your overall protocol design. After shipping several production bots across AMM integrations and perpetuals, here is what we have learned about the three libraries that actually see use in the Solana ecosystem.

Why Floating-Point Fails and What Replaces It

The BPF verifier classifies f32 and f64 operations as non-deterministic across validator hardware. The runtime will reject the program before execution. The practical replacement is scaled integers: you pick a decimal precision (most protocols use Q32.32 or a 10^9 scaling factor), represent every number as u64 or u128, and carry all arithmetic in that scaled space. The discipline this imposes is worth it — deterministic results across all validators, auditable overflow behavior, and predictable Compute Unit consumption.

The three libraries that cover most production use cases are fixed (arbitrary fixed-point types), uint (big-integer primitives for overflow-safe multiplication), and spl-math (Solana Program Library's purpose-built helpers). They are not interchangeable. Picking the wrong one costs you either CUs, precision, or correctness.

fixed: Ergonomic Fixed-Point Types With Real Trade-offs

The fixed crate gives you parameterized types like I64F64 (64 integer bits, 64 fractional bits) backed by i128 under the hood. The API is the most ergonomic of the three — operator overloading, saturation, wrapping, and checked variants all work naturally. For a taker fee calculation like amount * fee_bps / 10_000, the code reads cleanly and the precision is more than sufficient.

Cost in practice: a single I64F64 multiply compiles to a 128-bit integer multiplication plus a 64-bit right-shift for the scaling correction. In isolation that is roughly 4–6 CUs. String three of those together in a price impact formula and you are sitting at 15–20 CUs for that block — acceptable if you are not also running a Dutch auction loop in the same transaction.

The hard limit to know: I64F64's integer range tops out at 2^63. Protocols that express notional in native lamports (10^9 per SOL) and also need to track cumulative position sizes in the millions of SOL will hit that ceiling. Switch to a wider type (I96F32) or rethink your units before you ship.

uint: Big-Integer Arithmetic for Wide Intermediate Values

The uint crate (construct_uint! macro) is what Serum, Raydium, and most DEX contracts actually use under the hood. You define a stack-allocated integer of arbitrary width — U256, U512 — and carry your intermediates in that space before casting back down to u64 or u128. The pattern for fee-adjusted pricing looks like:

let numerator = U256::from(amount_in) * U256::from(fee_numerator);
let result = numerator / U256::from(fee_denominator);
u64::try_from(result).map_err(|_| ProgramError::ArithmeticOverflow)?

Precision and CU profile: this approach has essentially zero precision loss (you control the rounding explicitly) but the CU cost is higher than fixed for the same operation. A U256 multiply costs around 8–12 CUs because it decomposes into multiple 64-bit multiplications plus carry logic. Where it wins is safety: overflow is impossible by construction when your intermediates fit in 256 bits, and the try_from at the output boundary makes the failure path explicit in your program's error enum. For settlement and PnL calculations where a silent overflow would mean paying out wrong amounts, this explicitness is worth the CU premium.

spl-math: Purpose-Built for Solana Curves

The spl-math crate ships with the Solana Program Library and is specifically designed for swap curve pricing — constant product, stable swap, and the associated sqrt/log approximations. Its PreciseNumber type wraps u128 and works in a 12-decimal fixed-point space (scaling factor 10^12).

For its intended use case — computing token amounts for constant-product AMM swaps — it is the most tested option and the one most Solana auditors have already examined. The sqrt_precise function uses a Newton-Raphson iteration that converges in around 200–300 CUs, which is expensive but cheaper than any on-chain alternative for that operation.

Where it falls short: spl-math has almost no utility outside swap curve math. If you are building a perps program that needs mark price TWAP or funding rate computation, the missing operations mean you will be back to fixed or uint anyway. Treat it as a specialized tool, not a general arithmetic layer.

Mixing Libraries: What Actually Works in Production

In practice, non-trivial trading programs use all three. A sensible layering for a perpetuals-style program:

  • fixed (I64F64) for position accounting and PnL where precision matters and values stay in reasonable ranges
  • uint (U256) for fee calculations and any intermediate that multiplies two already-large numbers
  • spl-math only if you are computing a constant-product quote and want auditor familiarity

The critical discipline is unit consistency: document your scaling factor at each function boundary and add debug_assert! checks in non-production builds to verify that both inputs carry the same scale. Silent scale mismatches are the most common source of economic bugs in Solana programs — not overflow, not rounding, but two values in different units being added together.

Benchmarking CU Costs: What to Measure

The Solana solana_program::log::sol_log_compute_units() call lets you bracket any block of code and measure real CU consumption in a local validator or bankrun test. Do this before committing to a library. The numbers that matter for a trading program are:

  • Fee calculation path (called on every trade): target under 30 CUs
  • Price impact / slippage estimate: target under 100 CUs
  • Settlement / PnL close: can tolerate 200–400 CUs; only called once per position

If your math exceeds these, look at the intermediates. The most common fix is eliminating redundant type conversions between u64, u128, and library-specific types — each cast adds 1–3 CUs and they accumulate faster than you expect.


If you are building a trading program that handles real capital and need arithmetic that holds up under audit and adversarial conditions, reach out to TierZero. We have shipped these patterns in production and can save you the iteration cycles.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Smart Contracts#Solana#Rust#Trading Programs#Fixed-Point Arithmetic#On-Chain Math