mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-30 17:16:16 +00:00
- Remove Float from Core/SilverSightCore.lean (Receipt.pathCost is now Option Nat) - Prove TIC theorems tic_never_decreases and computation_generates_time - Add lakefile.lean, lean-toolchain, and .gitignore for .lake/ - Port Research-Stack Semantics.FixedPoint.lean to formal/CoreFormalism/FixedPoint.lean - Delete thin Q16_16_Spec.lean; update Python/QUBO comments and docs - Create AGENTS.md distilled from Research Stack core bindings - Update REBASE_RULES.md to strip legacy hacks and reference AGENTS.md Build: 2978 jobs, 0 errors (lake build)
222 lines
6.4 KiB
Python
222 lines
6.4 KiB
Python
"""Canonical Q16_16 fixed-point arithmetic.
|
|
|
|
Single source of truth: CoreFormalism/FixedPoint.lean
|
|
All operations MUST produce identical results to the Lean implementation.
|
|
|
|
Q16_16 represents fixed-point numbers with 16 integer bits and 16 fractional bits.
|
|
Range: [-32768.0, 32767.9999847412109375]
|
|
Resolution: 1/65536 ≈ 0.0000152587890625
|
|
|
|
CANONICAL ROUNDING MODE: round-half-up (banker's rounding)
|
|
- Values exactly at half-LSB round to nearest even
|
|
- All other values round to nearest
|
|
"""
|
|
|
|
import math
|
|
import struct
|
|
|
|
# ============================================================
|
|
# §1 CONSTANTS
|
|
# ============================================================
|
|
|
|
Q16_SCALE: int = 65536 # 2^16
|
|
Q16_MAX_RAW: int = 2147483647 # INT32_MAX
|
|
Q16_MIN_RAW: int = -2147483648 # INT32_MIN
|
|
Q16_MAX_FLOAT: float = 32767.9999847412109375 # Max representable
|
|
Q16_MIN_FLOAT: float = -32768.0 # Min representable
|
|
Q16_RESOLUTION: float = 1.0 / Q16_SCALE # ≈ 0.0000152587890625
|
|
|
|
|
|
# ============================================================
|
|
# §2 CONVERSIONS
|
|
# ============================================================
|
|
|
|
def float_to_q16(f: float) -> int:
|
|
"""Convert float to Q16_16 raw value with canonical round-half-up.
|
|
|
|
Uses banker's rounding: round(x * 65536) with ties to nearest even.
|
|
Result is clamped to [INT32_MIN, INT32_MAX].
|
|
|
|
Args:
|
|
f: Float value in range [-32768.0, 32767.9999847412109375]
|
|
|
|
Returns:
|
|
32-bit signed integer representing the Q16_16 value
|
|
|
|
Raises:
|
|
ValueError: If f is NaN or infinite
|
|
"""
|
|
if math.isnan(f) or math.isinf(f):
|
|
raise ValueError(f"Cannot convert non-finite float to Q16_16: {f}")
|
|
|
|
# Python's round() implements banker's rounding (round-half-to-even)
|
|
# round(x) = nearest integer, ties go to nearest even integer
|
|
scaled = f * Q16_SCALE
|
|
rounded = round(scaled) # Banker's rounding: ties to even
|
|
|
|
# Clamp to 32-bit signed range with saturation
|
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, rounded))
|
|
|
|
|
|
def q16_to_float(q: int) -> float:
|
|
"""Convert Q16_16 raw value to float.
|
|
|
|
This is exact: no rounding occurs.
|
|
|
|
Args:
|
|
q: 32-bit signed integer Q16_16 raw value
|
|
|
|
Returns:
|
|
Float value = q / 65536.0
|
|
"""
|
|
return q / Q16_SCALE
|
|
|
|
|
|
def int_to_q16(i: int) -> int:
|
|
"""Convert integer to Q16_16 raw value (exact, no rounding).
|
|
|
|
The integer is scaled by 65536. Clamped to valid range.
|
|
|
|
Args:
|
|
i: Integer in range [-32768, 32767]
|
|
|
|
Returns:
|
|
Q16_16 raw value = clamp(i * 65536)
|
|
"""
|
|
scaled = i * Q16_SCALE
|
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, scaled))
|
|
|
|
|
|
def q16_to_int(q: int) -> int:
|
|
"""Convert Q16_16 to integer (truncates toward zero).
|
|
|
|
Args:
|
|
q: Q16_16 raw value
|
|
|
|
Returns:
|
|
Integer part = q // 65536 (toward zero)
|
|
"""
|
|
# Python's // truncates toward negative infinity, so we need
|
|
# to handle negative values correctly for toward-zero truncation
|
|
if q >= 0:
|
|
return q // Q16_SCALE
|
|
else:
|
|
return -(-q // Q16_SCALE)
|
|
|
|
|
|
# ============================================================
|
|
# §3 ARITHMETIC OPERATIONS (all with saturation)
|
|
# ============================================================
|
|
|
|
def q16_add(a: int, b: int) -> int:
|
|
"""Add two Q16_16 values with saturation.
|
|
|
|
result = clamp(a + b)
|
|
"""
|
|
result = a + b
|
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, result))
|
|
|
|
|
|
def q16_sub(a: int, b: int) -> int:
|
|
"""Subtract two Q16_16 values with saturation.
|
|
|
|
result = clamp(a - b)
|
|
"""
|
|
result = a - b
|
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, result))
|
|
|
|
|
|
def q16_mul(a: int, b: int) -> int:
|
|
"""Multiply two Q16_16 values with canonical rounding.
|
|
|
|
result = canonical_round((a * b) / 65536)
|
|
|
|
Uses 64-bit intermediate, then applies banker's rounding.
|
|
"""
|
|
# Use Python's arbitrary precision integers (no overflow issue)
|
|
prod_64 = a * b
|
|
|
|
# Divide by scale with banker's rounding
|
|
# 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
|
|
|
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, rounded))
|
|
|
|
|
|
def q16_div(a: int, b: int) -> int:
|
|
"""Divide two Q16_16 values with canonical rounding.
|
|
|
|
result = canonical_round((a * 65536) / b)
|
|
|
|
Args:
|
|
a: Dividend (Q16_16 raw value)
|
|
b: Divisor (Q16_16 raw value), must not be zero
|
|
|
|
Raises:
|
|
ZeroDivisionError: If b is zero
|
|
"""
|
|
if b == 0:
|
|
raise ZeroDivisionError("Q16_16 division by zero")
|
|
|
|
# (a * 65536) / b with banker's rounding
|
|
num = a * Q16_SCALE
|
|
scaled = num / b # Float division for correct rounding
|
|
rounded = round(scaled)
|
|
|
|
return max(Q16_MIN_RAW, min(Q16_MAX_RAW, rounded))
|
|
|
|
|
|
# ============================================================
|
|
# §4 COMPARISON OPERATIONS
|
|
# ============================================================
|
|
|
|
def q16_eq(a: int, b: int) -> bool:
|
|
return a == b
|
|
|
|
def q16_lt(a: int, b: int) -> bool:
|
|
return a < b
|
|
|
|
def q16_le(a: int, b: int) -> bool:
|
|
return a <= b
|
|
|
|
|
|
# ============================================================
|
|
# §5 UTILITY FUNCTIONS
|
|
# ============================================================
|
|
|
|
def q16_from_bytes(raw_bytes: bytes) -> int:
|
|
"""Convert 4 bytes (little-endian int32) to Q16_16 raw value."""
|
|
return struct.unpack('<i', raw_bytes)[0]
|
|
|
|
|
|
def q16_to_bytes(q: int) -> bytes:
|
|
"""Convert Q16_16 raw value to 4 bytes (little-endian int32)."""
|
|
# Clamp first to ensure valid int32
|
|
clamped = max(Q16_MIN_RAW, min(Q16_MAX_RAW, q))
|
|
return struct.pack('<i', clamped)
|
|
|
|
|
|
def q16_is_valid(q: int) -> bool:
|
|
"""Check if a raw value is in the valid Q16_16 range."""
|
|
return Q16_MIN_RAW <= q <= Q16_MAX_RAW
|
|
|
|
|
|
def q16_repr(q: int) -> str:
|
|
"""Return human-readable representation of Q16_16 value."""
|
|
return f"Q16_16({q} / 65536 = {q16_to_float(q)})"
|
|
|
|
|
|
# ============================================================
|
|
# §6 EXPORTS FOR C INTEROP
|
|
# ============================================================
|
|
|
|
# These functions provide the C-compatible interface for the roundtrip test
|
|
def c_float_to_q16(f: float) -> int:
|
|
"""C-compatible wrapper for float_to_q16."""
|
|
return float_to_q16(f)
|
|
|
|
|
|
def c_q16_to_float(q: int) -> float:
|
|
"""C-compatible wrapper for q16_to_float."""
|
|
return q16_to_float(q)
|