FixedPointDecimal — exact decimal arithmetic, 50x–1000x faster than Foundation.Decimal

FixedPointDecimal — high-performance fixed-point decimal arithmetic for Swift

A few weeks ago we open sourced FuzzyMatch, our fuzzy string matching library built with Claude Code. That experiment went well enough that we applied the same approach to another important problem: exact decimal arithmetic that's fast enough for latency-sensitive financial systems.

The problem

Financial systems (or any system that wants to keep user input of decimal numbers correct) need exact decimal arithmetic — Double can't represent 0.1 exactly, causing accumulation errors.

Foundation.Decimal solves exactness but carries inherent overhead: it's a 20-byte variable-precision type with multi-word mantissa arithmetic. We contributed a fix to swift-foundation that removed heap allocations from Decimal's critical path (merged, ~3-5x improvement, should land with Swift 6.4), but even with that fix the architectural gap remains — variable-precision arithmetic is fundamentally more work than operating on a single machine integer.

For both throughput and latency-sensitive code paths processing tens- or hundred- of thousands of prices per tick, that overhead matters.

What it is

FixedPointDecimal is an @frozen struct backed by Int64, storing values with exactly 8 fractional decimal digits (value × 10⁸). All arithmetic compiles to native integer instructions with zero heap allocations.

let price: FixedPointDecimal = 123.45
let quantity: FixedPointDecimal = 1000
let notional = price * quantity // 123450

Eight fractional digits cover all practical financial instruments: cents (2), mils (3), basis points (4), FX pips (5), and cryptocurrency satoshis (8). The range (±92 billion) is sufficient for individual prices and quantities.

We have tried to follow standard library conventions and Decimal conventions as far as possible to make it easy to drop this in as a replacement if desired.

Performance

On Apple Silicon (M4 Max), compared to Foundation.Decimal:

Operation FixedPointDecimal Foundation.Decimal Speedup
Addition 0.67 ns 240 ns 359x
Comparison 0.33 ns 300 ns 901x
Hash 5 ns 261 ns 48x
Multiplication 8 ns 607 ns 79x
Division 8 ns 1,285 ns 168x
init(Double) 1.4 ns 2,319 ns 1,622x
rounded(scale:) 2 ns 705 ns 349x
JSON encode 320 ns 1,215 ns 3.8x
JSON decode 457 ns 831 ns 1.8x

Zero heap allocations across all operations. At 8 bytes vs Decimal's 20, arrays of prices use 40% of the memory — relevant when working with large datasets with millions of entries - both in memory, on disk or over the network.

Design choices

  • Banker's rounding everywhere — all entry points (string parsing, Double conversion, Decimal conversion, arithmetic) use round-half-to-even. The same input always produces the same stored value regardless of construction path.

  • Safe by default — trapping arithmetic matching Swift Int. Wrapping (&+, &-, &*) and overflow-reporting variants available.

  • NaN as sentinelInt64.min as the NaN value, with trapping semantics. No optional wrapper overhead.

  • @frozen — enables cross-module inlining and optimal ContiguousArray layout.

Protocol conformances

Sendable, BitwiseCopyable, AtomicRepresentable, Equatable, Hashable, Comparable, Numeric, SignedNumeric, Strideable, Codable, LosslessStringConvertible, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral SwiftUI: VectorArithmetic (animations), Plottable (Charts), FormatStyle, ParseableFormatStyle with full Decimal.FormatStyle forwarding for locale-aware currency/number/percent formatting.

Quality

549 tests across 15 suites, including property-based parity testing against Foundation.Decimal (10k iterations), exhaustive float-literal precision verification (1.1M+ values), and a libFuzzer fuzz harness with AddressSanitizer.

Acknowledgments

Like FuzzyMatch, this was built with Claude Code Opus 4.6 with careful guidance. The approach works well for self-contained, heavily testable components: we steer architecture and review every change, Claude writes the code, tests, benchmarks, and documentation. The overall implementation achieved in just two days — 15 source files, 13 test suites, benchmark suite, fuzz harness, DocC documentation — would have taken considerably longer manually.

Getting started

GitHub

API Documentation

If you need exact decimal arithmetic within the supported range with excellent performance — give it a try.

Joakim

24 Likes

Love this. Consider an option for "signalling" (trapping) nans.

1 Like

That probably makes sense instead, I think we will push out a 1.0.2 with that right now - even if formally a new major...(so not an option, but really the better safer default behaviour)

1 Like

It is!

Thank you. Still, consider making it an option at some later point.. I do love nans, how they propagate, always wanted something similar for integers (e.g. by sacrificing one of the bit patters) and that's exactly what you did. In some cases this is better than trapping.

FWIW, unlike floats my preference would be for Fixed.nan == itself. Didn't check your code in these regards yeah, all good here:

    /// NaN compares equal to itself, using sentinel semantics (not IEEE 754).
    /// This is required for `Hashable` and `Comparable` protocol correctness
    /// (strict total order).

Hashable correctness requires nan == nan? Don't think so... Comparable correctness - yes.

1 Like

You are of course correct, will fix docs.

Comparable doesn't either, or Float wouldn't implement it. NaN does behave weirdly for both hash-based collections and comparisons, though.

2 Likes