Documentation

Solanalib.Numeric.Fraction

Fraction: Q68.60 fixed-point numbers for Solana finance #

A fixed-point number with 60 fractional bits, modelled here at the spec layer with an unbounded Nat underlying representation (so omega works without ceremony). A future Fraction128 companion will refine this to the u128-bounded on-chain shape used by Kamino and other DeFi protocols (Kamino's U68F60).

A Fraction f represents the real number f.bits / 2^60.

This module is Layer 1 in the long-term Solanalib stack: a numeric foundation that every domain primitive (interest curves, exchange rates, slippage, vesting fractions, …) sits on top of. The conscious choice to use Nat rather than u128 at this layer follows the seL4 / CompCert pattern — prove mathematical properties on the abstract model, refine to the bounded representation separately.

Main definitions #

Main statements (this commit) #

A minimal kit:

Multiplication is approximately associative — the truncating division by scale after each multiplication introduces a rounding error of at most one ULP. A bound on that error is deferred to a follow-up file.

Conventions #

The "scale" exponent (60) is exposed as Fraction.scaleBits so future refinements that need a different precision (e.g. Q.32, Q.96) can be parameterised cleanly.

A fixed-point number with scaleBits fractional bits, where scaleBits = 60 to match the U68F60 convention common in Solana DeFi.

Fraction.mk b represents the real number b / 2^60.

Instances For
    theorem Solanalib.Fraction.ext {x y : Fraction} (bits : x.bits = y.bits) :
    x = y
    def Solanalib.instDecidableEqFraction.decEq (x✝ x✝¹ : Fraction) :
    Decidable (x✝ = x✝¹)
    Equations
    Instances For

      The number of fractional bits in the representation.

      Equations
      Instances For

        The scale factor: 2^scaleBits = 2^60. Every operation that preserves the fixed-point invariant divides intermediate products by this constant.

        Equations
        Instances For

          scale > 0 — useful for division-cancellation lemmas.

          The additive identity.

          Equations
          Instances For

            The multiplicative identity: encoding of 1.0.

            Equations
            Instances For

              Convert an integer n to its fixed-point encoding n * 2^60.

              Equations
              Instances For

                Project a fraction to its integer floor: bits / 2^60.

                Equations
                Instances For

                  Addition: just sum the bits. The fixed-point scale is preserved because both operands share it.

                  Equations
                  Instances For

                    Subtraction with an explicit underflow precondition: b ≤ a (on the underlying bit encoding). Returns ⟨a.bits − b.bits⟩. The precondition is mandatory: matches the dependent-type discipline used throughout Solanalib (compare Account.debit).

                    Equations
                    Instances For

                      Multiplication: (a.bits * b.bits) / scale. The division corrects the doubled scale that would otherwise result from bits * bits. This operation truncates — see the file docstring on associativity.

                      Equations
                      Instances For

                        Division with an explicit non-zero precondition on the divisor. Returns ⟨a.bits * scale / b.bits⟩ — the multiplication by scale corrects the lost-scale that would otherwise result from bits / bits.

                        Like every truncating division in Fraction, this rounds toward zero. A rounded-up companion (divCeil) is a future refinement.

                        Equations
                        Instances For

                          Order #

                          Fraction inherits its order from the underlying Nat encoding: a ≤ b ↔ a.bits ≤ b.bits. Comparisons are decidable and omega-friendly once unfolded via the iff lemmas.

                          @[implicit_reducible]
                          Equations
                          @[implicit_reducible]
                          Equations
                          @[implicit_reducible]
                          Equations
                          @[implicit_reducible]
                          Equations

                          Theorems #

                          @[simp]
                          @[simp]
                          theorem Solanalib.Fraction.sub_bits (a b : Fraction) (h : b.bits a.bits) :
                          (a.sub b h).bits = a.bits - b.bits
                          @[simp]
                          @[simp]
                          theorem Solanalib.Fraction.div_bits (a b : Fraction) (h : b.bits 0) :
                          (a.div b h).bits = a.bits * scale / b.bits

                          one.bits ≠ 0: useful for discharging div's precondition when the divisor is one.

                          Order theorems #

                          theorem Solanalib.Fraction.le_trans {a b c : Fraction} (h₁ : a b) (h₂ : b c) :
                          a c
                          theorem Solanalib.Fraction.le_antisymm {a b : Fraction} (h₁ : a b) (h₂ : b a) :
                          a = b

                          Round-trip theorems #

                          Integer round-trip: fromNat n |>.toFloor = n. The fixed-point encoding preserves integer values exactly.

                          theorem Solanalib.Fraction.sub_add (a b : Fraction) (h : b.bits a.bits) :
                          (a.sub b h).add b = a

                          sub followed by add cancels (within its precondition).

                          Division by one is identity.

                          Bridging to Lamports #

                          Lamports (= UInt64) and Fraction (= Q68.60) are the two numeric foundations Solana programs juggle: integer lamport counts and fractional ratios. The lift ofLamports is exact (no precision loss — Lamports fit comfortably in the 68 integer bits). The reverse projection toLamports? returns Option because the Fraction's integer floor can exceed u64::MAX.

                          Lift a Lamports (u64) to its exact Fraction encoding.

                          Equations
                          Instances For

                            Project a Fraction to Lamports by taking the integer floor. Returns none if the floor exceeds u64::MAX.

                            Equations
                            Instances For

                              Round-trip: (ofLamports l).toFloor = l.toNat. The Q68.60 lift preserves the integer value exactly.