TorchLean API

FloatLib.Kernels.LimbArray.Core.Proof

Limb arrays: value semantics of the accessors #

The accessors of Core.Runtime have value-level contracts in terms of the natural number toNat of a limb array. The contracts use three groups of lemmas.

The compiler certificate toNat_eq_toNatImpl replaces the proof-facing toNat by its Horner loop. limb_toNat_eq and limb_testBit connect limb access to value-level proofs.

Limbs and segments #

Limbs beyond the stored size read as zero.

A stored limb is the corresponding array element.

Every limb is below the radix.

The radix is 2^32.

A power of the radix is a power of two.

@[simp]

An empty segment denotes zero.

theorem FloatLib.Numerics.LimbArray.segment_succ (v : LimbArray) (s n : ) :
v.segment s (n + 1) = (v.limb s).toNat + radix * v.segment (s + 1) n

A segment is its first limb plus the radix times the rest, Horner's rule read from the low end.

A segment evaluates its zero-padded limb digits in Mathlib's little-endian convention.

theorem FloatLib.Numerics.LimbArray.segment_add (v : LimbArray) (s m n : ) :
v.segment s (m + n) = v.segment s m + radix ^ m * v.segment (s + m) n

Splitting a segment.

theorem FloatLib.Numerics.LimbArray.segment_succ_back (v : LimbArray) (s n : ) :
v.segment s (n + 1) = v.segment s n + (v.limb (s + n)).toNat * radix ^ n

Adding a limb at the top of a segment.

A segment of n limbs is below radix ^ n.

theorem FloatLib.Numerics.LimbArray.segment_congr {a b : LimbArray} {s n : } (h : i < n, a.limb (s + i) = b.limb (s + i)) :
a.segment s n = b.segment s n

Segments depend only on the limbs they cover.

theorem FloatLib.Numerics.LimbArray.segment_eq_zero_of_limb_eq_zero {v : LimbArray} {s n : } (h : i < n, v.limb (s + i) = 0) :
v.segment s n = 0

A segment of zero limbs is zero.

theorem FloatLib.Numerics.LimbArray.segment_eq_of_limb_eq_zero {v : LimbArray} {s m n : } (hmn : m n) (h : ∀ (i : ), m ii < nv.limb (s + i) = 0) :
v.segment s n = v.segment s m

Trailing zero limbs do not change a segment.

theorem FloatLib.Numerics.LimbArray.segment_eq_zero_iff {v : LimbArray} {s n : } :
v.segment s n = 0 i < n, v.limb (s + i) = 0

A segment is zero exactly when every covered limb is zero.

The value #

A limb array's value is the base-2^32 evaluation of its stored digits.

toNat may be read from any limb count at or above the stored size.

The value is below radix ^ size.

The value is below radix ^ n for every n at or above the stored size.

The value is below 2^(32 n) for every n at or above the stored size.

A segment starting at limb zero is a prefix of the value.

Limb i is the base-2^32 digit i of the value.

theorem FloatLib.Numerics.LimbArray.limb_testBit (v : LimbArray) (i j : ) (hj : j < 32) :
(v.limb i).toNat.testBit j = v.toNat.testBit (32 * i + j)

Bit j of limb i is bit 32 i + j of the value, for j < 32.

A limb has no bits at or above position 32.

theorem FloatLib.Numerics.LimbArray.ext_of_toNat {a b : LimbArray} (hsize : a.size = b.size) (h : a.toNat = b.toNat) :
a = b

Limb arrays with equal size and value are equal.

Limbs of two arrays with the same value agree.

theorem FloatLib.Numerics.LimbArray.segment_eq_mod_of_digits (v : LimbArray) (n c : ) :
(∀ j < c, (v.limb j).toNat = n / radix ^ j % radix)v.segment 0 c = n % radix ^ c

A value determined digit by digit.

Horner evaluation #

theorem FloatLib.Numerics.LimbArray.hornerFrom_eq (v : LimbArray) (n acc : ) :
v.hornerFrom n acc = acc * radix ^ n + v.segment 0 n

The compiled Horner loop computes the segment denotation from an accumulator.

The Horner loop computes the value.

@[csimp]

Compile the proof-facing front recursion of toNat as its tail-recursive Horner loop.

Construction from a natural number #

theorem FloatLib.Numerics.LimbArray.size_ofNatLoop (count n : ) (acc : Array UInt32) :
(ofNatLoop count n acc).size = acc.size + count

The ofNat loop appends exactly count limbs to its accumulator.

theorem FloatLib.Numerics.LimbArray.getElem_ofNatLoop (count n : ) (acc : Array UInt32) (j : ) (hj : j < (ofNatLoop count n acc).size) :
(ofNatLoop count n acc)[j] = if h : j < acc.size then acc[j] else UInt32.ofNat (n / radix ^ (j - acc.size))

The ofNat loop keeps the accumulator's prefix and appends the base-2^32 digits of n.

@[simp]
theorem FloatLib.Numerics.LimbArray.size_ofNat (n count : ) :
(ofNat n count).size = count

ofNat n count has exactly count limbs.

theorem FloatLib.Numerics.LimbArray.limb_ofNat (n count j : ) (hj : j < count) :
(ofNat n count).limb j = UInt32.ofNat (n / radix ^ j)

Limb j of ofNat n count is the j-th base-2^32 digit of n.

@[simp]
theorem FloatLib.Numerics.LimbArray.toNat_ofNat (n count : ) :
(ofNat n count).toNat = n % radix ^ count

The constructed array holds n modulo radix ^ count.

theorem FloatLib.Numerics.LimbArray.toNat_ofNat_of_lt {n count : } (h : n < radix ^ count) :
(ofNat n count).toNat = n

The constructed array holds n when n fits count limbs.

Rebuilding an array from its value and size is the identity.

Zero #

@[simp]
theorem FloatLib.Numerics.LimbArray.size_zero (count : ) :
(zero count).size = count

The zero array has the requested limb count.

theorem FloatLib.Numerics.LimbArray.limb_zero (count i : ) :
(zero count).limb i = 0

Every limb of the zero array is zero, also beyond its size.

@[simp]

The zero array denotes zero.

Leading limb, zero test, and logarithm #

theorem FloatLib.Numerics.LimbArray.topLimb_eq_zero_iff (v : LimbArray) (n : ) :
v.topLimb n = 0 i < n, v.limb i = 0

The top-limb search returns zero exactly when every scanned limb is zero.

The top-limb search never returns an index beyond the scanned prefix.

theorem FloatLib.Numerics.LimbArray.topLimb_spec (v : LimbArray) (n i : ) (h : v.topLimb n = i + 1) :
v.limb i 0 i < n ∀ (j : ), i < jj < nv.limb j = 0

When the search returns i + 1, limb i is the highest nonzero limb of the scanned prefix.

The zero test decides whether the value is zero.

The zero test as a decision.

@[simp]

Native UInt32.log2 has the natural-number value of Nat.log2.

The leading-bit position of a nonzero array is the natural-number logarithm of its value.

Bit access #

testBit reads the bit of the value.

theorem FloatLib.Numerics.LimbArray.lowMask32_toNat (r : ) (hr : r < 32) :
(lowMask32 r).toNat = 2 ^ r - 1

The mask of the low r bits.

A masked limb keeps exactly the bits below r.

@[simp]

The 32-bit window at lo is the value shifted down by lo, modulo the radix.

Masking and resizing #

@[simp]

Masking to the low bits keeps the limb count.

theorem FloatLib.Numerics.LimbArray.limb_lowBits (v : LimbArray) (k i : ) :
(v.lowBits k).limb i = if i < k / 32 then v.limb i else if i = k / 32 then v.limb i &&& lowMask32 (k % 32) else 0

lowBits keeps whole limbs below the cut, masks the limb at the cut, and zeroes the rest.

@[simp]

Masking keeps the value modulo 2^k.

@[simp]
theorem FloatLib.Numerics.LimbArray.size_resize (v : LimbArray) (count : ) :
(v.resize count).size = count

resize has exactly the requested limb count.

theorem FloatLib.Numerics.LimbArray.limb_resize (v : LimbArray) (count i : ) :
(v.resize count).limb i = if i < count then v.limb i else 0

resize keeps the limbs inside the new size and reads zero above it.

@[simp]
theorem FloatLib.Numerics.LimbArray.toNat_resize (v : LimbArray) (count : ) :
(v.resize count).toNat = v.toNat % radix ^ count

Resizing keeps the value modulo radix ^ count.

theorem FloatLib.Numerics.LimbArray.toNat_resize_of_size_le (v : LimbArray) {count : } (h : v.size count) :
(v.resize count).toNat = v.toNat

Resizing to at least the stored size keeps the value.

Sticky bits #

The sticky scan reports true exactly when some limb below the cut is nonzero.

anyBelow detects a nonzero suffix below bit k.

anyBelow as a decision.

@[simp]

Setting the sticky bit keeps the limb count.

theorem FloatLib.Numerics.LimbArray.limb_orLowBit_true (v : LimbArray) (hsize : 0 < v.size) (i : ) :
(v.orLowBit true).limb i = if i = 0 then v.limb 0 ||| 1 else v.limb i

Setting the sticky bit ors 1 into limb zero and leaves every other limb unchanged.

theorem FloatLib.Numerics.LimbArray.toNat_orLowBit (v : LimbArray) (hsize : 0 < v.size) (sticky : Bool) :
(v.orLowBit sticky).toNat = if sticky = true then v.toNat ||| 1 else v.toNat

Jamming a sticky bit sets bit zero of the value.

Comparison #

Lexicographic comparison from the top limb down agrees with comparing the denoted segments.

Limb comparison compares the values.