mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
fix(python): q16_mul/q16_div pure integer toward-zero truncation
Replaced float division + banker's rounding with pure integer division truncating toward zero, matching FixedPoint.lean:298 and FixedPoint.lean:302 exactly. Added _div_toward_zero helper for Python's // vs Lean Int / semantics mismatch. Updated docstring: 'CANONICAL ROUNDING MODE' → 'ARITHMETIC MODE: truncation toward zero matches Lean Int /'. No float in compute paths. float_to_q16 (I/O boundary) unchanged.
This commit is contained in:
parent
c3a5e6b60b
commit
68844c92c4
1 changed files with 33 additions and 30 deletions
|
|
@ -1,15 +1,16 @@
|
||||||
"""Canonical Q16_16 fixed-point arithmetic.
|
"""Canonical Q16_16 fixed-point arithmetic.
|
||||||
|
|
||||||
Single source of truth: CoreFormalism/Q16_16_Spec.lean
|
Single source of truth: Core/SilverSight/FixedPoint.lean
|
||||||
All operations MUST produce identical results to the Lean implementation.
|
All operations MUST produce identical results to the Lean implementation.
|
||||||
|
|
||||||
Q16_16 represents fixed-point numbers with 16 integer bits and 16 fractional bits.
|
Q16_16 represents fixed-point numbers with 16 integer bits and 16 fractional bits.
|
||||||
Range: [-32768.0, 32767.9999847412109375]
|
Range: [-32768.0, 32767.9999847412109375]
|
||||||
Resolution: 1/65536 ≈ 0.0000152587890625
|
Resolution: 1/65536 ≈ 0.0000152587890625
|
||||||
|
|
||||||
CANONICAL ROUNDING MODE: round-half-up (banker's rounding)
|
ARITHMETIC MODE: truncation toward zero (matches Lean Int / division)
|
||||||
- Values exactly at half-LSB round to nearest even
|
- Lean mul (FixedPoint.lean:298): (a.toInt * b.toInt) / q16Scale
|
||||||
- All other values round to nearest
|
- Lean div (FixedPoint.lean:302): (a.toInt * q16Scale) / b.toInt
|
||||||
|
- No rounding: Q16_16 is a quotient type with floor semantics
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
@ -126,45 +127,47 @@ def q16_sub(a: int, b: int) -> int:
|
||||||
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, result))
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, result))
|
||||||
|
|
||||||
|
|
||||||
def q16_mul(a: int, b: int) -> int:
|
def _div_toward_zero(num: int, den: int) -> int:
|
||||||
"""Multiply two Q16_16 values with canonical rounding.
|
"""Integer division truncating toward zero.
|
||||||
|
|
||||||
result = canonical_round((a * b) / 65536)
|
Python's // floors toward negative infinity.
|
||||||
|
Lean's Int / truncates toward zero.
|
||||||
Uses 64-bit intermediate, then applies banker's rounding.
|
This helper matches Lean's semantics.
|
||||||
"""
|
"""
|
||||||
# Use Python's arbitrary precision integers (no overflow issue)
|
if num >= 0:
|
||||||
prod_64 = a * b
|
return num // den
|
||||||
|
else:
|
||||||
# Divide by scale with banker's rounding
|
return -(-num // den)
|
||||||
# prod_64 / 65536 with half-to-even
|
|
||||||
scaled = prod_64 / Q16_SCALE # This is a float division for correct rounding
|
|
||||||
rounded = round(scaled) # Banker's rounding
|
def q16_mul(a: int, b: int) -> int:
|
||||||
|
"""Multiply two Q16_16 values with truncation toward zero.
|
||||||
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, rounded))
|
|
||||||
|
Matches FixedPoint.lean:298: (a.toInt * b.toInt) / q16Scale
|
||||||
|
|
||||||
|
Uses Python's arbitrary precision integers (no overflow).
|
||||||
|
"""
|
||||||
|
prod = a * b
|
||||||
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, _div_toward_zero(prod, Q16_SCALE)))
|
||||||
|
|
||||||
|
|
||||||
def q16_div(a: int, b: int) -> int:
|
def q16_div(a: int, b: int) -> int:
|
||||||
"""Divide two Q16_16 values with canonical rounding.
|
"""Divide two Q16_16 values with truncation toward zero.
|
||||||
|
|
||||||
result = canonical_round((a * 65536) / b)
|
Matches FixedPoint.lean:302: (a.toInt * q16Scale) / b.toInt
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
a: Dividend (Q16_16 raw value)
|
a: Dividend (Q16_16 raw value)
|
||||||
b: Divisor (Q16_16 raw value), must not be zero
|
b: Divisor (Q16_16 raw value), must not be zero
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ZeroDivisionError: If b is zero
|
ZeroDivisionError: If b is zero
|
||||||
"""
|
"""
|
||||||
if b == 0:
|
if b == 0:
|
||||||
raise ZeroDivisionError("Q16_16 division by zero")
|
raise ZeroDivisionError("Q16_16 division by zero")
|
||||||
|
|
||||||
# (a * 65536) / b with banker's rounding
|
|
||||||
num = a * Q16_SCALE
|
num = a * Q16_SCALE
|
||||||
scaled = num / b # Float division for correct rounding
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, _div_toward_zero(num, b)))
|
||||||
rounded = round(scaled)
|
|
||||||
|
|
||||||
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, rounded))
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue