6.3 KiB
tags
| tags | |||||||
|---|---|---|---|---|---|---|---|
|
Extending Beyond Physics: Currency as Custom Dimension
Try it live on Compiler Explorer{ .md-button }
Overview
mp-units is a general-purpose quantities and units library — everything that can be counted or measured benefits from its type-safe dimensional analysis. This example demonstrates creating a custom base dimension for currency, implementing type-safe currency conversions with exchange rates, and computing a multi-currency portfolio total.
Key Concepts
Custom Base Dimension
You can define entirely new base dimensions beyond the standard ISQ/SI system:
--8<-- "example/currency.cpp:48:48"
This creates a new fundamental dimension orthogonal to physical dimensions like length or mass.
Custom Units and Quantity Specification
Once you have a base dimension, define a quantity specification and its units:
--8<-- "example/currency.cpp:50:65"
Each currency becomes its own distinct unit, preventing accidental mixing of incompatible currencies. This is enforced at compile time:
--8<-- "example/currency.cpp:67:67"
This won't compile — EUR and USD quantities cannot be directly compared. The library framework cannot convert between them automatically because exchange rates are time-dependent — it does not know at what time point the conversion should happen.
User-Provided Runtime Conversion Functions
Since currency exchange rates vary over time, users must supply their own runtime conversion functions. The example implements a time-stamped rate lookup:
--8<-- "example/currency.cpp:69:81"
Note that paired rates are not exact inverses of each other: the asymmetry encodes the
bid/ask spread that a real market maker charges. The exchange_to functions apply the
appropriate rate and return the converted quantity:
--8<-- "example/currency.cpp:83:94"
Two overloads are provided: one for plain quantities (holdings, amounts) and one for
quantity_point (prices anchored to an absolute zero).
quantity vs quantity_point for Currency
The example uses both types deliberately:
-
quantity_pointfor prices — a price of $100 is an absolute reference; $0 is meaningful. Two prices in different currencies cannot be added — the type system rejects both forms:const quantity_point price_usd{100 * USD}; const quantity_point price_eur = exchange_to<euro>(price_usd, timestamp); // price_usd + price_eur // does not compile — points can't be added // price_usd.quantity_from_zero() // + price_eur.quantity_from_zero() // does not compile — different units (USD ≠ EUR) -
quantityfor holdings — a portfolio position is a displacement from zero; amounts in the same currency can be freely summed, but cross-currency addition is rejected until an explicit FX conversion is applied:const quantity pos_usd = 14'230 * USD; const quantity pos_eur = 4'902 * EUR; // pos_usd + pos_eur // does not compile — must convert first const quantity pos_eur_usd = round<USD>(exchange_to<us_dollar>(pos_eur, timestamp)).force_in<int>(); const quantity total_usd = pos_usd + pos_eur_usd; // ok — same unit
Multi-Currency Portfolio Valuation
Portfolio positions are held in their native currencies as plain quantity values.
Adding positions across currencies does not compile, making the type system enforce the
discipline of explicit FX conversion:
--8<-- "example/currency.cpp:112:124"
The rounding decision is made explicitly at the call site: round<USD> rounds to the
nearest whole dollar; floor<USD> or ceil<USD> could be substituted depending on the
desired settlement convention. .force_in<int>() changes the representation type to
int so the result stays consistent with the integer-valued input positions.
Example Usage
--8<-- "example/currency.cpp:96:132"
Sample Output:
100 USD -> 92.15 EUR
Portfolio positions:
14230 USD
4902 EUR => 5318 USD
1464 GBP => 1852 USD
890000 JPY => 6168 USD
Portfolio total (USD): 27568 USD
Why This Matters
- General-Purpose Library: Demonstrates mp-units working with non-physical measurable quantities
- Type Safety for Finance: Prevents mixing currencies without explicit conversion, caught at compile time
- Affine Space Design:
quantity_pointcorrectly models prices (absolute), whilequantitycorrectly models holdings (additive) - Explicit Rounding: The bid/ask spread and rounding convention are visible at the call site, not hidden inside a conversion helper
- Domain Modeling: The dimensional analysis pattern applies to any domain with unit-like concepts
This pattern can be adapted for other non-physical domains: cryptocurrencies, points/rewards systems, game economies, or any system where different "units" should not be accidentally mixed.
Practical Considerations
Currency conversions differ from typical physical unit conversions:
- Multiple independent units of the same quantity —
USD,EUR,GBP, andJPYare all units ofcurrency, so they participate correctly in quantity equations (e.g.,volume = currency × market_quantityholds for any currency unit). Yet they carry no compile-time conversion factor between them. Most units libraries cannot model this: either all units of a quantity are mutually convertible via a fixed ratio (impossible for FX), or they are defined as separate dimensions — which breaks dimensional equations sinceEUR × sharesandUSD × shareswould then be different, incompatible quantity specs. mp-units threads this needle withkind_of<currency>. - Exchange rates are time-dependent — the framework cannot know when you want to convert
- Paired rates are not exact inverses — the difference encodes the bid/ask spread
- Rounding convention matters —
round,floor, orceilproduce different results and the choice should be explicit at the call site - For fixed-point financial arithmetic with sub-cent precision, see the companion trade_execution example