~/bend-docscommunity

spec/math/fixed.bend source

spec/math/fixed.bend on the hub · documented module

import Baseimport ../lib/common.bend as Cimport ../../src/math/fixed.bend as Fimport ./number.bend as SN# Specification of src/math/fixed.bend: fixed-width integer operations as# Rust defines them for u32/u64 (python_style_math_stdlib_design.pdf# section 2.6: checked_add, wrapping_add, saturating_add, overflowing_add# families; section 3.2 bit_count and to_bytes/from_bytes; section 10# is_prime / next_prime).## Each clause is stated once over an instance model and read at U32# (U32.to_nat, width 32) and U64 (spec/math/w64.bend's value, width 64):#   val: T -> Nat, the value;  w, the width;  f, the function.# Widths are written with C.fits, C.low, C.high and C.shift (spec/lib/# common.bend): the checker never forms 2^32 or 2^64.##   family        clauses (Rust u32/u64 documentation)#   checked_*     CheckedAdd/Sub/Mul/Div/Rem/Pow/Shl/Shr.value: the exact#                 result when it fits (divisor nonzero, shift below w),#                 None otherwise#   wrapping_*    WrappingAdd/Mul/Pow/Shl/Shr.value: the result mod 2^w#                 (shift amount mod w); WrappingSub.value: r + b ==#                 a + 2^w * [a < b]#   saturating_*  SaturatingAdd/Mul/Pow.value: the result when it fits,#                 else the largest value (fits w, +1 does not);#                 SaturatingSub.value: a - b floored at 0#   overflowing_* OverflowingX.value (the wrapping value) and#                 OverflowingX.flag (the result did not fit / a < b /#                 shift >= w)#   bit_count     BitCount.value: spec/math/number.bend's ones(w, value)#   is_prime      IsPrime.value (U32): spec/math/number.bend's prime#   next_prime    NextPrime.found, NextPrime.none (U32): the least prime#                 above n below 2^32, or None when there is none#   to_bytes_*    ToBytes.le, ToBytes.be: the w/8 base-256 digits#   from_bytes_*  FromBytes.le, FromBytes.be: the value of exactly w/8#                 digits below 256, None for any other list# ---- shared definitions ----def mval(~T: Data, ~val: T -> Nat, r: Maybe<&2, T>) -> Maybe<&2, Nat>:  match r:    case Some{x}:      Some{val(x)}    case None{}:      None{}def keep(ok: Bool, +n: Nat) -> Maybe<&2, Nat>:  match ok:    case True{}:      Some{n}    case False{}:      None{}# n when it fits w bitsdef cnat(+w: Nat, +n: Nat) -> Maybe<&2, Nat>:  keep(C.fits(w, n), n)def ov_val(~T: Data, ~val: T -> Nat, r: F.Over<T>) -> Nat:  match r:    case F.OV{v, f}:      val(v)def ov_flag(-T: Data, r: F.Over<T>) -> Bool:  match r:    case F.OV{v, f}:      fdef bn(b: Bool) -> Nat:  match b:    case True{}:      1n    case False{}:      0n# r is n when n fits w bits, and the largest w-bit value otherwisedef saturated(+w: Nat, +n: Nat, +r: Nat, ok: Bool) -> Bool:  match ok:    case True{}:      Nat.is_eq(r, n)    case False{}:      Bool.and(C.fits(w, r), Bool.not(C.fits(w, 1n+r)))# ---- checked ----def CheckedAdd.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> Maybe<&2, T>, +w: Nat, +a: T, +b: T) -> Type:  {mval(~T, ~val, f(a, b)) == cnat(w, Nat.add(val(a), val(b))) : Maybe<&2, Nat>}def CheckedSub.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> Maybe<&2, T>, +a: T, +b: T) -> Type:  {mval(~T, ~val, f(a, b)) == keep(Nat.is_le(val(b), val(a)), Nat.sub(val(a), val(b))) : Maybe<&2, Nat>}def CheckedMul.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> Maybe<&2, T>, +w: Nat, +a: T, +b: T) -> Type:  {mval(~T, ~val, f(a, b)) == cnat(w, Nat.mul(val(a), val(b))) : Maybe<&2, Nat>}def CheckedDiv.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> Maybe<&2, T>, +a: T, +b: T) -> Type:  {mval(~T, ~val, f(a, b)) == keep(Bool.not(Nat.is_eq(val(b), 0n)), Nat.div(val(a), val(b))) : Maybe<&2, Nat>}def CheckedRem.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> Maybe<&2, T>, +a: T, +b: T) -> Type:  {mval(~T, ~val, f(a, b)) == keep(Bool.not(Nat.is_eq(val(b), 0n)), Nat.mod(val(a), val(b))) : Maybe<&2, Nat>}def CheckedPow.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> Maybe<&2, T>, +w: Nat, +a: T, +e: U32) -> Type:  {mval(~T, ~val, f(a, e)) == cnat(w, Nat.pow(val(a), U32.to_nat(e))) : Maybe<&2, Nat>}def CheckedShl.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> Maybe<&2, T>, +w: Nat, +a: T, +s: U32) -> Type:  {mval(~T, ~val, f(a, s)) == keep(Nat.is_lt(U32.to_nat(s), w), C.low(w, C.shift(U32.to_nat(s), val(a)))) : Maybe<&2, Nat>}def CheckedShr.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> Maybe<&2, T>, +w: Nat, +a: T, +s: U32) -> Type:  {mval(~T, ~val, f(a, s)) == keep(Nat.is_lt(U32.to_nat(s), w), C.high(U32.to_nat(s), val(a))) : Maybe<&2, Nat>}# ---- wrapping ----def WrappingAdd.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> T, +w: Nat, +a: T, +b: T) -> Type:  {val(f(a, b)) == C.low(w, Nat.add(val(a), val(b))) : Nat}def WrappingSub.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> T, +w: Nat, +a: T, +b: T) -> Type:  {Nat.add(val(f(a, b)), val(b)) == Nat.add(val(a), C.shift(w, bn(Nat.is_lt(val(a), val(b))))) : Nat}def WrappingMul.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> T, +w: Nat, +a: T, +b: T) -> Type:  {val(f(a, b)) == C.low(w, Nat.mul(val(a), val(b))) : Nat}def WrappingPow.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> T, +w: Nat, +a: T, +e: U32) -> Type:  {val(f(a, e)) == C.low(w, Nat.pow(val(a), U32.to_nat(e))) : Nat}def WrappingShl.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> T, +w: Nat, +a: T, +s: U32) -> Type:  {val(f(a, s)) == C.low(w, C.shift(Nat.mod(U32.to_nat(s), w), val(a))) : Nat}def WrappingShr.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> T, +w: Nat, +a: T, +s: U32) -> Type:  {val(f(a, s)) == C.high(Nat.mod(U32.to_nat(s), w), val(a)) : Nat}# ---- saturating ----def SaturatingAdd.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> T, +w: Nat, +a: T, +b: T) -> Type:  {saturated(w, Nat.add(val(a), val(b)), val(f(a, b)), C.fits(w, Nat.add(val(a), val(b)))) == True{} : Bool}def SaturatingSub.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> T, +a: T, +b: T) -> Type:  {val(f(a, b)) == Nat.sub(val(a), val(b)) : Nat}def SaturatingMul.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> T, +w: Nat, +a: T, +b: T) -> Type:  {saturated(w, Nat.mul(val(a), val(b)), val(f(a, b)), C.fits(w, Nat.mul(val(a), val(b)))) == True{} : Bool}def SaturatingPow.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> T, +w: Nat, +a: T, +e: U32) -> Type:  {saturated(w, Nat.pow(val(a), U32.to_nat(e)), val(f(a, e)), C.fits(w, Nat.pow(val(a), U32.to_nat(e)))) == True{} : Bool}# ---- overflowing ----def OverflowingAdd.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> F.Over<T>, +w: Nat, +a: T, +b: T) -> Type:  {ov_val(~T, ~val, f(a, b)) == C.low(w, Nat.add(val(a), val(b))) : Nat}def OverflowingAdd.flag(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> F.Over<T>, +w: Nat, +a: T, +b: T) -> Type:  {ov_flag(T, f(a, b)) == Bool.not(C.fits(w, Nat.add(val(a), val(b)))) : Bool}def OverflowingSub.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> F.Over<T>, +w: Nat, +a: T, +b: T) -> Type:  {Nat.add(ov_val(~T, ~val, f(a, b)), val(b)) == Nat.add(val(a), C.shift(w, bn(Nat.is_lt(val(a), val(b))))) : Nat}def OverflowingSub.flag(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> F.Over<T>, +a: T, +b: T) -> Type:  {ov_flag(T, f(a, b)) == Nat.is_lt(val(a), val(b)) : Bool}def OverflowingMul.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> F.Over<T>, +w: Nat, +a: T, +b: T) -> Type:  {ov_val(~T, ~val, f(a, b)) == C.low(w, Nat.mul(val(a), val(b))) : Nat}def OverflowingMul.flag(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: T -> F.Over<T>, +w: Nat, +a: T, +b: T) -> Type:  {ov_flag(T, f(a, b)) == Bool.not(C.fits(w, Nat.mul(val(a), val(b)))) : Bool}def OverflowingPow.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> F.Over<T>, +w: Nat, +a: T, +e: U32) -> Type:  {ov_val(~T, ~val, f(a, e)) == C.low(w, Nat.pow(val(a), U32.to_nat(e))) : Nat}def OverflowingPow.flag(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> F.Over<T>, +w: Nat, +a: T, +e: U32) -> Type:  {ov_flag(T, f(a, e)) == Bool.not(C.fits(w, Nat.pow(val(a), U32.to_nat(e)))) : Bool}def OverflowingShl.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> F.Over<T>, +w: Nat, +a: T, +s: U32) -> Type:  {ov_val(~T, ~val, f(a, s)) == C.low(w, C.shift(Nat.mod(U32.to_nat(s), w), val(a))) : Nat}def OverflowingShl.flag(~T: Data, ~f: @+x: T -> @+y: U32 -> F.Over<T>, +a: T, +s: U32, +w: Nat) -> Type:  {ov_flag(T, f(a, s)) == Bool.not(Nat.is_lt(U32.to_nat(s), w)) : Bool}def OverflowingShr.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> @+y: U32 -> F.Over<T>, +w: Nat, +a: T, +s: U32) -> Type:  {ov_val(~T, ~val, f(a, s)) == C.high(Nat.mod(U32.to_nat(s), w), val(a)) : Nat}def OverflowingShr.flag(~T: Data, ~f: @+x: T -> @+y: U32 -> F.Over<T>, +a: T, +s: U32, +w: Nat) -> Type:  {ov_flag(T, f(a, s)) == Bool.not(Nat.is_lt(U32.to_nat(s), w)) : Bool}# ---- bit count, primality ----def BitCount.value(~T: Data, ~val: T -> Nat, ~f: @+x: T -> Nat, +w: Nat, +a: T) -> Type:  {f(a) == SN.ones(w, val(a)) : Nat}def IsPrime.value(+a: U32) -> Type:  {F.u32_is_prime(a) == SN.prime(U32.to_nat(a)) : Bool}# no prime m with lo <= m < lo + kdef no_prime(k: Nat, +lo: Nat) -> Bool:  match k:    case 0n:      True{}    case 1n+p:      Bool.and(Bool.not(SN.prime(lo)), no_prime(p, 1n+lo))# a found p is a prime above n, and none lies strictly between themdef NextPrime.found(+n: U32, +p: U32, +h: {F.u32_next_prime(n) == Some{p} : Maybe<&2, U32>}) -> Type:  {Bool.and(Bool.and(Nat.is_lt(U32.to_nat(n), U32.to_nat(p)), SN.prime(U32.to_nat(p))), no_prime(Nat.sub(U32.to_nat(p), 1n+U32.to_nat(n)), 1n+U32.to_nat(n))) == True{} : Bool}# None: no prime m with n < m < 2^32def NextPrime.none(+n: U32, +h: {F.u32_next_prime(n) == None{} : Maybe<&2, U32>}, +m: Nat, +hm: {Nat.is_lt(U32.to_nat(n), m) == True{} : Bool}, +hf: {C.fits(32n, m) == True{} : Bool}) -> Type:  {SN.prime(m) == False{} : Bool}# ---- bytes ----def nats(xs: List<&2, U32>) -> List<&2, Nat>:  match xs:    case Nil{}:      Nil{}    case Con{x, t}:      Con{U32.to_nat(x), nats(t)}# the k lowest base-256 digits of n, least significant firstdef le_digits(k: Nat, +n: Nat) -> List<&2, Nat>:  match k:    case 0n:      Nil{}    case 1n+p:      Con{C.low(8n, n), le_digits(p, C.high(8n, n))}def ToBytes.le(~T: Data, ~val: T -> Nat, ~f: @+x: T -> List<&2, U32>, +nb: Nat, +a: T) -> Type:  {nats(f(a)) == le_digits(nb, val(a)) : List<&2, Nat>}def ToBytes.be(~T: Data, ~val: T -> Nat, ~f: @+x: T -> List<&2, U32>, +nb: Nat, +a: T) -> Type:  {nats(f(a)) == C.reverse(Nat, le_digits(nb, val(a))) : List<&2, Nat>}# the value of exactly k base-256 digits, least significant first; None# for another length or a digit of 256 or moredef digit_on(r: Maybe<&2, Nat>, ok: Bool, +x: Nat) -> Maybe<&2, Nat>:  match r:    case Some{+v}:      match ok:        case True{}:          Some{Nat.add(x, C.shift(8n, v))}        case False{}:          None{}    case None{}:      None{}def from_le(k: Nat, xs: List<&2, Nat>) -> Maybe<&2, Nat>:  match k xs:    case 0n Nil{}:      Some{0n}    case 1n+p Con{+x, t}:      digit_on(from_le(p, t), C.fits(8n, x), x)    case _ _:      None{}def FromBytes.le(~T: Data, ~val: T -> Nat, ~f: List<&2, U32> -> Maybe<&2, T>, +nb: Nat, bs: List<&2, U32>) -> Type:  {mval(~T, ~val, f(bs)) == from_le(nb, nats(bs)) : Maybe<&2, Nat>}def FromBytes.be(~T: Data, ~val: T -> Nat, ~f: List<&2, U32> -> Maybe<&2, T>, +nb: Nat, bs: List<&2, U32>) -> Type:  {mval(~T, ~val, f(bs)) == from_le(nb, C.reverse(Nat, nats(bs))) : Maybe<&2, Nat>}