""" SilverSight.FixedPoint.Q16_16 — Julia Port Mirrors `Core/SilverSight/FixedPoint.lean` (Lean 4). Design rules: * Semantic value is a signed bounded `Int32` raw integer. * Saturation is performed by `ofRawInt`. * Float is external boundary only (`ofFloat`, `toFloat`). All operations match Lean semantics: saturation, rounding mode, and constant values are verified against the Lean `#eval` witnesses in `Q16_16Numerics.lean`. """ module Q16_16 export Q16_16Value, Q16_SCALE, of_raw_int, to_int, zero, one, epsilon, two, half, q16_min_raw, q16_max_raw, q16_scale, add, sub, mul, div_op, neg, abs_, sqrt, ln_nat, exp, exp_neg, sin, pow, of_float, to_float, lt, le, gt, ge, q16_pct1, q16_pct70, q16_pct30, q16_150pct const q16_min_raw = -2147483648 const q16_max_raw = 2147483647 const q16_scale = 65536 const Q16_SCALE = q16_scale """ Q16_16Value Signed Q16.16 fixed-point raw integer, saturated into `[q16_min_raw, q16_max_raw]`. Concrete type: `Int32` (matches `ofBits`/`toBits` hardware representation). """ const Q16_16Value = Int32 """ clamp_raw(raw::Int) -> Int Saturating clamp matching Lean `q16Clamp`. Returns `q16_min_raw` if `raw < q16_min_raw`, `q16_max_raw` if `raw > q16_max_raw`, else `raw`. """ function clamp_raw(raw::Int)::Int if raw > q16_max_raw return q16_max_raw elseif raw < q16_min_raw return q16_min_raw else return raw end end """ of_raw_int(raw) -> Q16_16Value Match Lean `Q16_16.ofRawInt`: saturating clamp into valid range. """ function of_raw_int(raw::Integer)::Q16_16Value Q16_16Value(clamp_raw(Int(raw))) end of_raw_int(raw::Int32)::Q16_16Value = raw """ to_int(q) -> Int Match Lean `Q16_16.toInt`. """ to_int(q::Q16_16Value)::Int = Int(q) # ── Constants (match Lean `#eval` witnesses exactly) ──────────────────────── const zero = Q16_16Value(0) const one = Q16_16Value(65536) const neg_one = Q16_16Value(-65536) const epsilon = Q16_16Value(1) # Lean: epsilon = ⟨1, ...⟩ const two = Q16_16Value(131072) # 2 * q16_scale const half = Q16_16Value(32768) # q16_scale / 2 const max_val = Q16_16Value(q16_max_raw) const min_val = Q16_16Value(q16_min_raw) const infinity = max_val # NUVMAP constants (from projection_engine.py) const q16_pct1 = Q16_16Value(655) # 0.01 floor const q16_pct70 = Q16_16Value(45875) # 0.7 floor const q16_pct30 = Q16_16Value(19661) # 0.3 ceil (banker's) const q16_150pct = Q16_16Value(98304) # 1.5 exact # Mathemtical constants (from Q16_16Numerics.lean) const pi_val = Q16_16Value(205887) # π const e_val = Q16_16Value(178145) # e const ln2_val = Q16_16Value(45426) # ln(2) const sqrt2_val = Q16_16Value(92682) # √2 # ── Arithmetic (saturating, matching Lean) ────────────────────────────────── """ add(a, b) Saturating addition. Matches Lean `Q16_16.add`. """ add(a::Q16_16Value, b::Q16_16Value)::Q16_16Value = of_raw_int(to_int(a) + to_int(b)) """ sub(a, b) Saturating subtraction. Matches Lean `Q16_16.sub`. """ sub(a::Q16_16Value, b::Q16_16Value)::Q16_16Value = of_raw_int(to_int(a) - to_int(b)) """ mul(a, b) Saturating Q16.16 multiplication with banker's rounding: `round((a * b) / 65536)`. Matches Python `q_mul` semantics. Uses `Int64` intermediate. """ function mul(a::Q16_16Value, b::Q16_16Value)::Q16_16Value prod = Int64(to_int(a)) * Int64(to_int(b)) of_raw_int(Int(round(prod / q16_scale))) end """ div_op(a, b) Saturating Q16.16 division with banker's rounding: `round((a * 65536) / b)`. Matches Python `q_div` semantics. Returns `infinity` if `b == 0`. Uses `Int64` intermediate. """ function div_op(a::Q16_16Value, b::Q16_16Value)::Q16_16Value if to_int(b) == 0 return infinity end prod = Int64(to_int(a)) * Int64(q16_scale) of_raw_int(Int(round(prod / Int64(to_int(b))))) end """ neg(a) Saturating negation. Matches Lean `Q16_16.neg`. """ neg(a::Q16_16Value)::Q16_16Value = of_raw_int(-to_int(a)) """ abs_(a) Absolute value. Matches Lean `Q16_16.abs`. """ abs_(a::Q16_16Value)::Q16_16Value = to_int(a) < 0 ? neg(a) : a # ── Comparisons ───────────────────────────────────────────────────────────── lt(a::Q16_16Value, b::Q16_16Value)::Bool = to_int(a) < to_int(b) le(a::Q16_16Value, b::Q16_16Value)::Bool = to_int(a) ≤ to_int(b) gt(a::Q16_16Value, b::Q16_16Value)::Bool = to_int(a) > to_int(b) ge(a::Q16_16Value, b::Q16_16Value)::Bool = to_int(a) ≥ to_int(b) # ── Float boundary (external only) ────────────────────────────────────────── """ of_float(f) Float → Q16_16. Matches Lean `Q16_16.ofFloat`: - NaN or ≥ 32768.0 → infinity - ≤ -32768.0 → min_val - floor(f * 65536) with sign handling External boundary only. """ function of_float(f::Float64)::Q16_16Value if isnan(f) || f ≥ 32768.0 return infinity elseif f ≤ -32768.0 return min_val else return of_raw_int(Int(floor(f * 65536.0))) end end of_float(f::Float32)::Q16_16Value = of_float(Float64(f)) """ to_float(q) Q16_16 → Float64. Matches Lean `Q16_16.toFloat`. """ to_float(q::Q16_16Value)::Float64 = Float64(to_int(q)) / 65536.0 # ── Numeric functions ─────────────────────────────────────────────────────── """ int_sqrt(n) Integer square root via Newton's method (floor). Matches Lean `intSqrt`. Fuel-limited to 64 iterations. """ function int_sqrt(n::Int)::Int n ≤ 0 && return 0 x = div(n, 2) + 1 for _ in 1:64 x_new = div(x + div(n, x), 2) x_new ≥ x && return x x = x_new end return x end """ sqrt(q) Q16.16 square root via integer Newton. Matches Lean `Q16_16.sqrt`. """ sqrt(q::Q16_16Value)::Q16_16Value = to_int(q) ≤ 0 ? zero : of_raw_int(int_sqrt(to_int(q) * q16_scale)) """ ln_nat(q) Natural logarithm approximation around 1.0. Matches Lean `Q16_16.ln`. """ function ln_nat(q::Q16_16Value)::Q16_16Value x = to_int(q) x ≤ 0 && return zero y = x - q16_scale y2 = div(y * y, q16_scale) y3 = div(y * y2, q16_scale) of_raw_int(y - div(y2, 2) + div(y3, 3)) end """ exp_neg(x) e^(-x) piecewise approximation. Matches Lean `Q16_16.expNeg`. """ function exp_neg(x::Q16_16Value)::Q16_16Value rx = to_int(x) if rx ≥ 0x00030000; return zero elseif rx ≥ 0x00020000; return of_raw_int(0x00004D29) elseif rx ≥ 0x00010000; return of_raw_int(0x0000C5C0) else return of_raw_int(0x0001C5C0) end end """ exp(x) Q16.16 exponential via Taylor with range reduction eˣ = 2ᵏ·eʳ. Matches Lean `Q16_16.exp`. """ function exp(x::Q16_16Value)::Q16_16Value rx = to_int(x) if rx ≤ -q16_scale; return zero elseif rx ≥ 4 * q16_scale; return max_val end ln2_raw = 45426 k = div(rx, ln2_raw) r = rx - k * ln2_raw r2 = div(r * r, q16_scale) r3 = div(r * r2, q16_scale) r4 = div(r2 * r2, q16_scale) r5 = div(r2 * r3, q16_scale) r6 = div(r3 * r3, q16_scale) taylor = q16_scale + r + div(r2, 2) + div(r3, 6) + div(r4, 24) + div(r5, 120) + div(r6, 720) shifted = if k ≥ 15; q16_max_raw elseif k ≤ -15; 0 elseif k ≥ 0; taylor << UInt(k) else; taylor >> UInt(-k) end of_raw_int(shifted) end """ sin(x) Q16.16 sine via 7th-order Taylor with quadrant reduction. Matches Lean `Q16_16.sin`. """ function sin(x::Q16_16Value)::Q16_16Value pi_raw = 205887 two_pi_raw = 411774 raw = to_int(x) % two_pi_raw raw = raw < 0 ? raw + two_pi_raw : raw half_pi = div(pi_raw, 2) q = if raw < half_pi; 0 elseif raw < pi_raw; 1 elseif raw < pi_raw + half_pi; 2 else; 3 end reduced = if q == 0; raw elseif q == 1; pi_raw - raw elseif q == 2; raw - pi_raw else; two_pi_raw - raw end t = reduced t2 = div(t * t, q16_scale) t3 = div(t * t2, q16_scale) t5 = div(t3 * t2, q16_scale) t7 = div(t5 * t2, q16_scale) sin_pos = t - div(t3, 6) + div(t5, 120) - div(t7, 5040) result = q < 2 ? sin_pos : -sin_pos of_raw_int(max(q16_min_raw, min(q16_max_raw, result))) end """ pow(base, e) Q16.16 power: base^e = exp(e * ln(base)). Matches Lean `Q16_16.pow`. """ function pow(base::Q16_16Value, e::Q16_16Value)::Q16_16Value to_int(base) ≤ 0 && return to_int(e) == 0 ? one : zero to_int(e) == 0 && return one to_int(e) == q16_scale && return base return exp(mul(ln_nat(base), e)) end # ── NUVMAP convenience ────────────────────────────────────────────────────── """ q_mul(a, b) — Q16.16 multiplication with banker's rounding. q_div(a, b) — Q16.16 division with banker's rounding. Returns 0 if b == 0. q_add(a, b) — Saturating addition. q_sub(a, b) — Saturating subtraction. These match the naming in `python/nuvmap/projection_engine.py` and `tests/test_nuvmap_equivalence.py`. """ q_mul(a::Q16_16Value, b::Q16_16Value)::Q16_16Value = mul(a, b) q_div(a::Q16_16Value, b::Q16_16Value)::Q16_16Value = div_op(a, b) q_add(a::Q16_16Value, b::Q16_16Value)::Q16_16Value = add(a, b) q_sub(a::Q16_16Value, b::Q16_16Value)::Q16_16Value = sub(a, b) # ── Instance methods for convenience ────────────────────────────────────── Base.:+(a::Q16_16Value) = a Base.:-(a::Q16_16Value) = neg(a) Base.:+(a::Q16_16Value, b::Q16_16Value) = add(a, b) Base.:-(a::Q16_16Value, b::Q16_16Value) = sub(a, b) Base.:*(a::Q16_16Value, b::Q16_16Value) = mul(a, b) Base.:/(a::Q16_16Value, b::Q16_16Value) = div_op(a, b) Base.:<(a::Q16_16Value, b::Q16_16Value) = lt(a, b) Base.:<=(a::Q16_16Value, b::Q16_16Value) = le(a, b) Base.:>(a::Q16_16Value, b::Q16_16Value) = gt(a, b) Base.:>=(a::Q16_16Value, b::Q16_16Value) = ge(a, b) Base.abs(a::Q16_16Value) = abs_(a) Base.sqrt(a::Q16_16Value) = sqrt(a) Base.exp(a::Q16_16Value) = exp(a) Base.sin(a::Q16_16Value) = sin(a) Base.:^(a::Q16_16Value, b::Q16_16Value) = pow(a, b) end