mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
- floorDiv returns int64 instead of int32 to match avmClamp signature - instr.Arg wrapped in int64() for avmClamp/avmQ0Clamp calls - Test expected value cast to int32 fix(julia): add Q16_SCALE export to Q16_16 module
361 lines
10 KiB
Julia
361 lines
10 KiB
Julia
"""
|
|
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
|