# Copyright 2026 Arthur Strauss
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Fixed-point arithmetic for QUA-compatible numeric representation.
Author: Arthur Strauss
Date: 2026-02-08
"""
[docs]
class FixedPoint:
"""Fixed-point numeric type for QUA-compatible saturated arithmetic.
Values are stored as signed integers scaled by ``2**fractional_bits`` within
a configurable bit width.
"""
def __init__(self, value, fractional_bits=28, bit_width=32):
"""Create a fixed-point value from a float or integer scale.
Args:
value: Initial value (float interpreted in fixed-point units).
fractional_bits: Number of fractional bits in the representation.
bit_width: Total signed bit width including the sign bit.
"""
self.fractional_bits = fractional_bits
self.scale = 1 << fractional_bits
self.bit_width = bit_width
self.max_value = (1 << (bit_width - 1)) - 1
self.min_value = -(1 << (bit_width - 1))
self.value = self._saturate(int(value * self.scale))
def _saturate(self, value):
if value > self.max_value:
return self.max_value
elif value < self.min_value:
return self.min_value
else:
return value
def __add__(self, other):
if isinstance(other, FixedPoint):
result = self.value + other.value
elif isinstance(other, int):
result = self.value + (other << self.fractional_bits)
else:
raise TypeError("Unsupported operand type(s) for +: 'FixedPoint' and '{}'".format(type(other).__name__))
return FixedPoint(self._saturate(result) / self.scale, self.fractional_bits, self.bit_width)
def __sub__(self, other):
if isinstance(other, FixedPoint):
result = self.value - other.value
elif isinstance(other, int):
result = self.value - (other << self.fractional_bits)
else:
raise TypeError("Unsupported operand type(s) for -: 'FixedPoint' and '{}'".format(type(other).__name__))
return FixedPoint(self._saturate(result) / self.scale, self.fractional_bits, self.bit_width)
def __mul__(self, other):
if isinstance(other, FixedPoint):
result = (self.value * other.value) >> self.fractional_bits
elif isinstance(other, int):
result = self.value * other
else:
raise TypeError("Unsupported operand type(s) for *: 'FixedPoint' and '{}'".format(type(other).__name__))
return FixedPoint(self._saturate(result) / self.scale, self.fractional_bits, self.bit_width)
def __truediv__(self, other):
if isinstance(other, FixedPoint):
result = (self.value << self.fractional_bits) // other.value
elif isinstance(other, int):
result = self.value // other
else:
raise TypeError("Unsupported operand type(s) for /: 'FixedPoint' and '{}'".format(type(other).__name__))
return FixedPoint(self._saturate(result) / self.scale, self.fractional_bits, self.bit_width)
def __lshift__(self, other):
result = self.value << other
return FixedPoint(self._saturate(result) / self.scale, self.fractional_bits, self.bit_width)
def __rshift__(self, other):
result = self.value >> other
return FixedPoint(self._saturate(result) / self.scale, self.fractional_bits, self.bit_width)
def __and__(self, other):
if isinstance(other, FixedPoint):
result = self.value & other.value
elif isinstance(other, int):
result = self.value & other
else:
raise TypeError("Unsupported operand type(s) for &: 'FixedPoint' and '{}'".format(type(other).__name__))
return FixedPoint(result / self.scale, self.fractional_bits, self.bit_width)
def __or__(self, other):
if isinstance(other, FixedPoint):
result = self.value | other.value
elif isinstance(other, int):
result = self.value | other
else:
raise TypeError("Unsupported operand type(s) for |: 'FixedPoint' and '{}'".format(type(other).__name__))
return FixedPoint(result / self.scale, self.fractional_bits, self.bit_width)
def __repr__(self):
return f"{self.value / self.scale:.10f}"
[docs]
def to_int(self) -> int:
"""Return the value rounded toward zero as a Python integer."""
return self.value >> self.fractional_bits
[docs]
def to_unsafe_int(self) -> int:
"""Return the raw scaled integer without fractional-bit shifting."""
return self.value
[docs]
def to_float(self) -> float:
"""Return the value as a Python float."""
return self.value / self.scale
[docs]
@classmethod
def from_int(cls, int_value, fractional_bits=28, bit_width=32):
"""Construct a :class:`FixedPoint` from an integer fixed-point representation.
Args:
int_value: Scaled integer value (already in fixed-point units).
fractional_bits: Number of fractional bits.
bit_width: Total signed bit width.
Returns:
New :class:`FixedPoint` instance.
"""
return cls(int_value / (1 << fractional_bits), fractional_bits, bit_width)