Dimensional Algebra Rules
This document defines the rules for dimensional arithmetic. While examples use Solidity syntax, these algebraic rules are universal and apply to any language performing fixed-point or scaled arithmetic.
Overview
Dimensional Algebra Rules
This document defines the rules for dimensional arithmetic. While examples use Solidity syntax, these algebraic rules are universal and apply to any language performing fixed-point or scaled arithmetic.
Notation
{A}- A semantic unit (e.g.,{tok},{share},{UoA})D18- A precision prefix indicating 18 decimal placesD18{A}- A value with unit{A}and precision D18{A/B}- A derived unit (A per B){A*B}- A compound unit (A times B){1}- Dimensionless (pure ratio)
Formal Grammar
annotation := scale? "{" dimension "}"
scale := "D" number
dimension := base_dim | derived_dim | "1"
derived_dim := dimension "/" dimension | dimension "*" dimension
base_dim := identifier
Examples:
{tok}- Token amount (no scale specified)D18{tok}- Token amount, 18 decimals fixed-pointD27{USD/tok}- Price, 27 decimals fixed-point{1}- Dimensionless (pure number or ratio)
Basic Composition Rules
Multiplication
Dimensions multiply when values are multiplied:
{A} * {B} = {A*B}
Examples:
{tok} * {UoA/tok} = {UoA} # tokens × price = value
{share} * {tok/share} = {tok} # shares × exchange rate = tokens
{1} * {A} = {A} # dimensionless preserves dimension
Division
Dimensions divide when values are divided:
{A} / {B} = {A/B}
Examples:
{tok} / {share} = {tok/share} # exchange rate
{UoA} / {tok} = {UoA/tok} # price
{A} / {A} = {1} # same dimensions cancel
{A} / {1} = {A} # dividing by dimensionless preserves
Addition and Subtraction
CRITICAL: Addition and subtraction require identical dimensions.
{A} + {A} = {A} # Valid
{A} - {A} = {A} # Valid
{A} + {B} = ERROR # Invalid! Dimension mismatch
{A} - {B} = ERROR # Invalid! Dimension mismatch
Examples:
{tok} + {tok} = {tok} # Valid: adding token amounts
{tok} + {share} = ERROR # Invalid: can't add tokens and shares
{UoA/tok} + {UoA/tok} = {UoA/tok} # Valid: adding prices
Precision Arithmetic
Multiplication Precision
Precisions ADD when multiplying:
D18 * D18 = D36
D27 * D18 = D45
D18 * D27 = D45
Example:
D18{tok} * D18{share/tok} = D36{share} # Need to scale down by D18
Division Precision
Precisions SUBTRACT when dividing:
D36 / D18 = D18
D27 / D18 = D9
D18 / D18 = D0 (integer)
Example:
D36{share} / D18 = D18{share} # Scaling down
D27{UoA/tok} / D18{1} = D9{UoA/tok} # Precision reduced
Scaling Operations
Scaling is multiplication/division by a pure precision constant:
D18{A} * D9 = D27{A} # Scale up precision
D27{A} / D9 = D18{A} # Scale down precision
D36{A} / D18 = D18{A} # Common pattern after multiplication
Common Patterns
Price Calculation
// Calculate value from amount and price
// {UoA} = {tok} * D27{UoA/tok} / D27
// D27{UoA} = D18{tok} * D27{UoA/tok} / D18
uint256 value = Math.mulDiv(amount, price, D18);
Share Conversion (ERC4626)
// Convert assets to shares
// {share} = {tok} * {share} / {tok}
// D18{share} = D18{tok} * D18{share} / D18{tok}
uint256 shares = Math.mulDiv(assets, totalSupply, totalAssets);
// Convert shares to assets
// {tok} = {share} * {tok} / {share}
uint256 assets = Math.mulDiv(shares, totalAssets, totalSupply);
Fee Application
// Apply percentage fee
// {tok} = {tok} * D18{1} / D18
uint256 fee = Math.mulDiv(amount, feeRate, D18);
uint256 netAmount = amount - fee;
Rate Per Second
// Calculate accrued amount
// {tok} = {tok} * D18{1/s} * {s} / D18
uint256 accrued = Math.mulDiv(principal, rate * elapsed, D18);
Cross-Rate Calculation
// Calculate token A price in terms of token B
// D27{B/A} = D27{UoA/A} * D27 / D27{UoA/B}
uint256 crossRate = Math.mulDiv(priceA, D27, priceB);
Dimensional Simplification
Cancellation
When the same unit appears in numerator and denominator, it cancels:
{tok/share} * {share} = {tok} # share cancels
{UoA/tok} * {tok/BU} = {UoA/BU} # tok cancels
{A/B} * {B/C} = {A/C} # B cancels
Identity
{A} * {1} = {A}
{A} / {1} = {A}
{A} * {B/B} = {A} # Multiplying by 1
Multi-Step Calculations
For complex expressions, track dimensions step by step:
// Calculate share value in UoA
// Step 1: {tok/share} = {tok} / {share}
// Step 2: {UoA/share} = {tok/share} * {UoA/tok}
//
// In code:
// D18{UoA/share} = D18{tok} * D27{UoA/tok} / D18{share} / D9
uint256 shareValue = Math.mulDiv(
Math.mulDiv(totalAssets, price, totalShares),
1,
1e9 // Scale D27 to D18
);
Error Patterns
Division Before Multiplication
Risky for precision loss:
// BAD: May lose precision
uint256 result = a / b * c;
// BETTER: Use mulDiv
uint256 result = Math.mulDiv(a, c, b);
Missing Scaling
Common bug pattern:
// BUG: D36 result stored in D18 variable
uint256 result = amount * rate; // D18 * D18 = D36!
// CORRECT:
uint256 result = Math.mulDiv(amount, rate, D18);
Wrong Scaling Direction
// BUG: Multiplied when should divide
uint256 price18 = price27 * 1e9; // Now D36!
// CORRECT:
uint256 price18 = price27 / 1e9;
Special Cases
Dimensionless Constants
Integer constants like 2, 100, MAX_UINT are dimensionless {1}:
uint256 doubled = amount * 2; // {tok} * {1} = {tok}
uint256 half = amount / 2; // {tok} / {1} = {tok}
Timestamps
Timestamps and durations have dimension {s} (seconds):
uint256 elapsed = block.timestamp - lastUpdate; // {s} - {s} = {s}
uint256 rate = feePerSecond * elapsed; // {1/s} * {s} = {1}
Basis Points
Basis points are {1} with implicit D4 precision:
uint256 constant BPS = 10000; // D4
uint256 fee = amount * feeBps / BPS; // {tok} * {1} / {1} = {tok}
Validation Checklist
For any arithmetic operation:
- ✓ Do operand dimensions combine correctly?
- ✓ Is the result dimension what's expected?
- ✓ Is precision handled correctly (scaling)?
- ✓ Is rounding direction appropriate?
- ✓ Could intermediate values overflow?