All SIMDs
SIMD · 0607

Remove Floating Point from Runtime

DraftCoreStandardAugust 21, 2026
by Tao Zhu (Anza), Tom Pointon (Firedancer)

Summary

Replace floating-point arithmetic in consensus-critical inflation and rent calculations with deterministic integer arithmetic.

Motivation

Inflation rewards and rent parameters affect capitalization, account state, and bank hashes. These values must be computed identically by all validator clients, without relying on IEEE-754 behavior or platform math-library functions such as powf.

Alternatives Considered

General-purpose rational arithmetic

Use a general-purpose rational type for all fractional calculations. This provides exact arithmetic but adds unnecessary complexity and overhead compared with the bounded integer representation proposed here.

New Terminology

No new terminologies, to clarify few terms used here:

  • Scaled rate: A fractional rate represented as an integer with the fixed denominator RATE_SCALE.
  • Decay-per-slot: The scaled rate used to apply one slot of inflation decay.
  • Anchor rate: The inflation rate from which subsequent decay is calculated.

Detailed Design

Protocol percentages and ratios are represented as:

Fraction { numerator: u64, denominator: nonzero u64 }

Multiplication by a fraction is defined as:

mul_fraction_floor(value, fraction) = floor(value * fraction.numerator / fraction.denominator)

The multiplication MUST use a widened intermediate at least 128 bits wide, or an exactly equivalent implementation. Rounding MUST be toward zero.

Rust-like pseudocode:

struct Fraction {
    numerator: u64,
    denominator: NonZeroU64,
}

fn mul_fraction_floor(value: u64, fraction: Fraction) -> u64 {
    let product: u128 =
        u128::from(value) * u128::from(fraction.numerator);
    let quotient: u128 =
        product / u128::from(fraction.denominator.get());

    // Integer division discards any remainder; no round-to-nearest is applied.
    u64::try_from(quotient).expect("protocol constants keep the result in u64")
}

Inflation Rewards

The scaled constants below are the integer protocol representation of the existing inflation schedule. Current validator implementations may store these values as floating point, but after activation clients MUST use the integer representation for consensus calculations.

RATE_SCALE = 1_000_000_000_000_000

INITIAL_RATE = 80_000_000_000_000 // existing 0.08 initial inflation TERMINAL_RATE = 15_000_000_000_000 // existing 0.015 terminal inflation PICO_RATE = 100_000_000_000 // existing 0.0001 pico inflation

TAPER_15 = Fraction { numerator: 15, denominator: 100 } TAPER_30 = Fraction { numerator: 30, denominator: 100 }

Each protocol slot-time regime MUST define slots_per_year as an exact rational value. For any slot-time regimes already active or specified by prior features, this SIMD preserves their existing protocol values by converting the current decimal constants into exact rational constants. For known slot-time regimes, the value for slots_per_year are:

slot-timeslots_per_year
400ms78_892_314_984 / 1000
350ms90_162_645_696 / 1000
300ms105_189_753_312 / 1000
250ms126_227_703_974 / 1000
200ms157_784_629_968 / 1000

Epoch inflation rewards are computed as:

annual_reward = floor(capitalization * validator_rate / RATE_SCALE)

epoch_reward = floor(annual_reward slots_in_epoch slots_per_year_denominator / slots_per_year_numerator)

This two-step flooring is normative.

Feature Activation and Reward Boundary

The integer reward calculation applies immediately in the boundary bank. If the feature activates on epoch E -> E+1, rewards paid for epoch E during E+1 must use fixed-point math. Rewards calculated by banks before this feature activation must use legacy calculation.

Inflation Decay

Floating-point exponentiation is replaced with scaled integer decay.

For each supported taper and slot-time regime, the protocol defines a constant:

decay_per_slot = round_to_nearest_even((1 - taper)^(1 / slots_per_year) * RATE_SCALE)

These constants are part of the protocol specification. Validator clients MUST NOT derive them at runtime with floating-point arithmetic. For each supported slot-time regimes and known taper, the value of decay_per_slot are:

slot-timetaperdecay_per_slot
400ms15%999_999_997_939_990
350ms15%999_999_998_197_492
300ms15%999_999_998_454_993
250ms15%999_999_998_712_494
200ms15%999_999_998_969_995
400ms30%999_999_995_478_965
350ms30%999_999_996_044_094
300ms30%999_999_996_609_224
250ms30%999_999_997_174_353
200ms30%999_999_997_739_482

Scaled multiplication is:

mul_scaled_floor(a, b) = floor(a * b / RATE_SCALE)

Decay over n slots must use right-to-left binary exponentiation with mul_scaled_floor at each multiplication:

pseudocode:

  result = RATE_SCALE
  while n > 0:
      if n is odd:
          result = mul_scaled_floor(result, base)
      n = floor(n / 2)
      if n > 0:
          base = mul_scaled_floor(base, base)

Because mul_scaled_floor floors after each multiplication, clients must use this association order exactly.

The annual validator rate is:

rate = max(TERMINAL_RATE, floor(anchor_rate * decay_since_anchor / RATE_SCALE))

Before SIMD-0550 activation, anchor_rate = INITIAL_RATE and the taper is 15%. After SIMD-0550 activation, clients compute the activation anchor_rate using the pre-activation integer schedule, then apply the 30% taper from that activation slot forward. No floating-point re-anchoring of initial is used.

Rent

SIMD-0194 has already deprecated the rent exemption threshold on mainnet-beta since epoch 943.

Client implementations that need to replay the SIMD-0194 activation boundary must use an integer-equivalent conversion for the historical migration:

if exemption_threshold == bytes(1.0): lamports_per_byte = lamports_per_byte

if exemption_threshold == bytes(2.0): lamports_per_byte = checked_mul(lamports_per_byte, 2)

Backwards Compatibility

This change is consensus-affecting and requires feature activation at an epoch boundary.

Impact

No transaction or program interfaces are changed.

Validator implementations must use the specified arithmetic and rounding rules after feature activation. Differences from the previous floating-point calculation may occur at rounding boundaries.

Expected epoch reward drift

Using the following method to calculate legacy f64 epoch reward and fixed-point reward:

  • f64 path:
year = epoch_start_slot / slots_per_year_f64
validator_rate = max(0.015, 0.08 * (1 - taper)^year)

epoch_reward = trunc(validator_rate * capitalization * slots_in_epoch /
                     slots_per_year_f64)
  • fixed-point path:
slots_since_anchor = epoch_start_slot - anchor_slot
decay = pow_scaled_floor(decay_per_slot, slots_since_anchor)
validator_rate = max(TERMINAL_RATE, floor(INITIAL_RATE * decay / RATE_SCALE))
annual_reward = floor(capitalization * validator_rate / RATE_SCALE)
epoch_reward = floor(annual_reward * slots_in_epoch * slots_per_year_denominator
                     / slots_per_year_numerator)

For a normalized capitalization of 1,000,000,000 SOL, 432,000-slot epochs, and the current 400ms, 350ms, 300ms, 250ms, and 200ms slot-time regimes, the largest observed validator epoch reward difference over 6000 epochs is:

  • 15% taper: 31,831,625 lamports, or 0.031831625 SOL per epoch
  • 30% taper: 14,181,312 lamports, or 0.014181312 SOL per epoch

The observed relative difference: abs(fixed_point_epoch_reward - f64_epoch_reward) / f64_epoch_reward, is below 2.1e-7.

Security Considerations

These calculations affect consensus-critical state. Implementations must use the specified arithmetic widths, constants, and rounding rules to avoid cross-client divergence.

Intermediate arithmetic must not overflow, and protocol constants must not be recomputed using floating-point arithmetic at runtime.

Sequencing with SIMD-0550

This feature must be activated before or at the same epoch boundary as SIMD-0550. When SIMD-0550 activates, clients compute the SIMD-0550 activation anchor rate using pre-SIMD-0550 integer 15% taper schedule, then apply the integer 30% taper from the SIMD-0550 activation slot forward.

Conformance

Conformance tests MUST include:

  • epoch reward rounding vectors
  • terminal-rate clamping
  • decay vectors for every protocol slot-time regime
  • SIMD-0550 activation anchoring
  • replay across activation with matching bank hashes