def _add(a, b): assert isinstance(a, FixedPoint) assert isinstance(b, FixedPoint) promoted_qformat = QFormat.from_qformats(a.qformat, b.qformat) result_qformat = QFormat(promoted_qformat.integer_bits + 1, promoted_qformat.fraction_bits) lhs_op = FixedPoint(a, result_qformat) rhs_op = FixedPoint(b, result_qformat) result_numerator = lhs_op._numerator + rhs_op._numerator return FixedPoint._from_numerator(result_numerator, result_qformat)
def _truediv(dividend, divisor): assert isinstance(dividend, FixedPoint) assert isinstance(divisor, FixedPoint) result_qformat = QFormat( dividend.qformat.integer_bits + divisor.qformat.fraction_bits + 1, divisor.qformat.integer_bits + dividend.qformat.fraction_bits) working_qformat = QFormat.from_qformats(dividend.qformat, divisor.qformat, result_qformat) lhs_op = FixedPoint(dividend, working_qformat) rhs_op = FixedPoint(divisor, working_qformat) # We use Fraction's round() here rather than floor division to get correct rounding working_numerator = round( Fraction(lhs_op._numerator * working_qformat.denominator, rhs_op._numerator)) working_result = FixedPoint._from_numerator(working_numerator, working_qformat) return FixedPoint(working_result, result_qformat)
def _mul(a, b): assert isinstance(a, FixedPoint) assert isinstance(b, FixedPoint) result_qformat = QFormat( a.qformat.integer_bits + b.qformat.integer_bits + 1, a.qformat.fraction_bits + b.qformat.fraction_bits) lhs_op = FixedPoint(a, result_qformat) rhs_op = FixedPoint(b, result_qformat) result_numerator = (lhs_op._numerator * rhs_op._numerator) // result_qformat.denominator return FixedPoint._from_numerator(result_numerator, result_qformat)
def _from_integer(cls, i): """Create a FixedPoint using a QFormat with sufficient precision to represent the integer. Args: i: The integer to be represented in FixedPoint. Returns: A FixedPoint representation with sufficient precision to represent i. """ assert isinstance(i, int) num_bits = i.bit_length() + 1 # Additional bit for sign information qformat = QFormat(num_bits, 0) return cls._from_numerator(i, qformat)
def _truediv(dividend, divisor): assert isinstance(dividend, FixedPoint) assert isinstance(divisor, FixedPoint) result_qformat = QFormat(dividend.qformat.integer_bits + divisor.qformat.fraction_bits + 1, divisor.qformat.integer_bits + dividend.qformat.fraction_bits) working_qformat = QFormat.from_qformats(dividend.qformat, divisor.qformat, result_qformat) lhs_op = FixedPoint(dividend, working_qformat) rhs_op = FixedPoint(divisor, working_qformat) # We use Fraction's round() here rather than floor division to get correct rounding working_numerator = round(Fraction(lhs_op._numerator * working_qformat.denominator, rhs_op._numerator)) working_result = FixedPoint._from_numerator(working_numerator, working_qformat) return FixedPoint(working_result, result_qformat)
def _pow(base, exponent): assert isinstance(base, FixedPoint) assert isinstance(base, FixedPoint) if exponent.is_integer(): integer_exponent = abs(floor(exponent)) result_qformat = QFormat( max(base.qformat.integer_bits - 1, 0) * integer_exponent + 1, base.qformat.fraction_bits * integer_exponent) result_numerator = base._numerator**integer_exponent positive_result = FixedPoint._from_numerator(result_numerator, result_qformat) if exponent >= 0: return positive_result else: reciprocal_result = 1 / positive_result return reciprocal_result return float(base)**float(exponent)
def _from_float(cls, f): """Create a FixedPoint using a QFormat without loss of precision. Args: f (float): A float of which to create an equivalent FixedPoint representation. Returns: A FixedPoint object the QFormat for which will have sufficient precision to exactly represent the float. Raises: OverflowError: If f is NaN or infinite. """ assert isinstance(f, Real) if isnan(f) or isinf(f): raise OverflowError("{} cannot be represented by {}".format( f, cls.__name__)) # 1. Work out where the binary point is fr, exp = frexp(f) numerator = int(fr * (2**53)) # 53 binary places in the fraction binary_point_index = 53 - exp # Is one based # 2. Work out how many significant figures there are to the left of the binary point; call this m most_significant_set_index = numerator.bit_length() - 1 num_leading_bits = 1 + most_significant_set_index - binary_point_index m = max(num_leading_bits, 0) + 1 # One to accommodate sign # 3. Work out how many significant figures there are to the right of the binary point; call this n lowest_bit = lowest_set_bit(numerator) least_significant_set_index = lowest_bit.bit_length() - 1 num_trailing_bits = binary_point_index - least_significant_set_index n = max(num_trailing_bits, 0) # 4. Make QFormat(m, n) qformat = QFormat(m, n) # 5. Shift the numerator to fit Qm.n # We want the binary point to be at index n shift = binary_point_index - n shifted_numerator = signed_left_shift(numerator, shift) return cls._from_numerator(shifted_numerator, qformat)
def _from_rational_exact(cls, r): """Create a FixedPoint using a QFormat with sufficient precision to represent the integer. Args: r: A rational number to be represented exactly. Returns: An exact FixedPoint representation of r. Raises: ValueError: If r cannot be represented exactly. """ assert isinstance(r, Rational) integer_part = trunc(r) fraction_part = abs(r - integer_part) binary_numerator, binary_denominator = fraction_with_base( fraction_part, base=2) qformat = QFormat(integer_part.bit_length() + 1, int(log2(binary_denominator))) return cls._from_numerator(binary_numerator, qformat)
def __neg__(self): # This can overflow for the most negative value of the current QFormat - the positive value can't be # represented - so the result must have one additional bit of precision. result_qformat = QFormat(self._qformat.integer_bits + 1, self._qformat.fraction_bits) return FixedPoint._from_numerator(-self._numerator, result_qformat)