"""Canonical Q16_16 fixed-point arithmetic. Single source of truth: CoreFormalism/Q16_16_Spec.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(' 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(' 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)