Initialization

Initializing from a float

When initializing a FixedPoint from a float, signed, m, and n are all optional. Thus the following object instantiations are all valid:

>>> from fixedpoint import FixedPoint
>>> from math import pi
>>> a = FixedPoint(pi) # No signed, m, or n argument
>>> float(a), a.qformat
(3.141592653589793, 'UQ2.48')

>>> b = FixedPoint(pi, True) # No m or n argument
>>> float(b), b.qformat
(3.141592653589793, 'Q3.48')

>>> c = FixedPoint(2**-80, m=10, n=80) # No signed argument
>>> float(c), c.qformat
(8.271806125530277e-25, 'UQ10.80')

Warning

Python’s float type is typically implemented with 64-bit doubles, specified by IEEE 754. This means that there’s only 53 significant binary bits in a float (you can verify this by examining sys.float_info), and thus precision and resolution may be compromised in certain circumstances. See The Python Tutorial on the Issues and Limitations of Floating Point Arithmetic.

>>> print(format(pi, '.60f'))
3.141592653589793115997963468544185161590576171875000000000000
>>> a = FixedPoint(pi, n=60, str_base=2)
>>> print(str(a))
11001001000011111101101010100010001000010110100011000000000000

In the example above, \(\pi\) is only representable by 52 floating point mantissa bits. Thus extending the decimal representation out to 60 places, the bits eventually stabilize to 0 (even though \(\pi\) is irrational and never stabilizes). While the FixedPoint representation of the float is accurate, this method of representing \(\pi\) suffers from inaccuracy.

Initializing from an int

When initializing a FixedPoint from an int, signed, m, and n are all optional. When n is left unspecified, it is guaranteed to be 0 (since integers never require fractional bits). When m is left unspecified, FixedPoint.min_m() is used to deduce the number of integer bits needed to represent init, and after rounding occurs, trim() is used to remove superfluous leading 0s or sign-extended 1s.

>>> a = FixedPoint(-2**1000, n=42) # No signed or m argument
>>> a.qformat
'Q1001.42'

>>> b = FixedPoint(14, True, 10) # No n argument
>>> b.qformat, float(b)
('Q10.0', 14.0)

>>> c = FixedPoint(0, 0, 19, 88) # Signed, m, and n arguments are present
>>> str(c)
'000000000000000000000000000'

Tip

Python’s int type has unlimited precision, meaning it can be as large as you need it to be! In fact, the FixedPoint bits are stored internally as an int.

Initializing from a str

When initializing a FixedPoint from a str, signed, m, and n are required:

>>> a = FixedPoint('1') # no Q format
Traceback (most recent call last):
    ...
ValueError: String literal initialization Q format must be fully constrained.

The string is converted to an int and this value is stored internally as the FixedPoint bits. This means that leading 0s are ignored and not included in the total number of bits:

>>> a = FixedPoint('0x00000000000000001', signed=0, m=1, n=0) # Leading 0s
>>> a.bits
1

Rounding and overflow handling are not performed. If the bits of the string exceed the specified Q format, a ValueError is raised.

>>> a = FixedPoint('0xFF', 0, 1, 1) # A whole bunch of extra bits
Traceback (most recent call last):
   ...
ValueError: Superfluous bits detected in string literal '0xFF' for UQ1.1 format.

int(init, 0) is used to internally convert the str to int, thus the 0b, 0o, or 0x radix is required for binary, octal, or hexadecimal strings, respectively. If the radix is not present, it is considered a decimal number. This means if you have an integer that represents some known/desired Q format, you can simply call bin(), oct(), str or hex() to convert it to a str, then to a FixedPoint. This method of initialization may be super useful in generating random stimulus:

>>> import random
>>> random.seed(42)
>>> signed, m, n = 1, 12, 13
>>> bits = random.getrandbits(m + n)
>>> x = FixedPoint(hex(bits), signed, m, n)
>>> float(x), x.qformat
(-1476.9078369140625, 'Q12.13')

Warning

Converting a negative integer to a string is not allowed; the bits of interest should be masked before converting to a string.

>>> x = FixedPoint('-1', signed=1, m=2, n=0)
Traceback (most recent call last):
   ...
ValueError: Superfluous bits detected in string literal '-1' for Q2.0 format.

>>> m, n = 2, 0
>>> mask = 2**(m + n) - 1
>>> x = FixedPoint(str(-1 & mask), 1, m, n)
>>> float(x), x.qformat
(-1.0, 'Q2.0')

Initializing from another FixedPoint

When initializing a FixedPoint from another FixedPoint, only init is required; all other arguments are ignored.

>>> x = FixedPoint(2**-5)
>>> x.qformat
'UQ0.5'

>>> y = FixedPoint(x, n=492) # n is ignored
>>> y.qformat
'UQ0.5'

Initializing from other types

When init is not a(n) float, int, str, or FixedPoint, the object is cast to a float.

>>> from decimal import Decimal
>>> x = Decimal(1) / Decimal(10)
>>> y = FixedPoint(x)
>>> z = FixedPoint(0.1)
>>> y == z
True

If this fails, a TypeError is raised.

>>> file = open('some_file', 'a')
>>> FixedPoint(file)
Traceback (most recent call last):
    ...
TypeError: Unsupported type <class '_io.TextIOWrapper'>; cannot convert to float.

Initializers

When the Q format of a FixedPoint should stay the same, but a different value is needed, it is quicker to use one of the following initializers than generating a new FixedPoint object:

It’s quicker because the Q format and properties need not be validated.

from timeit import timeit

test_constructor = {
    'stmt': 'FixedPoint(hex(random.getrandbits(100)), 1, 42, 58)',
    'setup': 'from fixedpoint import FixedPoint; import random',
    'number': 100000,
}

test_initializer = {
    'stmt': 'x.from_string(hex(random.getrandbits(100)))',
    'setup': 'import fixedpoint, random; x = fixedpoint.FixedPoint(0, 1, 42, 58)',
    'number': 100000,
}

print("Constructor:", timeit(**test_constructor))
print("Initializer:", timeit(**test_initializer))

The code block above tests the difference between creating 100,000 FixedPoint instances and 100,000 initializer calls. There is a significant time improvement by using the initializer from_string() instead of the FixedPoint constructor:

Constructor: 1.6910291
Initializer: 0.18432009999999988

The results are similar with from_int():

Constructor: 1.8801342
Initializer: 0.4326542

and from_float() (using random.random() with 50 fractional bits):

Constructor: 2.4381994
Initializer: 0.7997059000000002

One example is when stimulus is being created that uses a single FixedPoint at a time, or when the processing of that FixedPoint does not change its properties. Create a single instance, and then just use the initializer in each loop iteration:

from fixedpoint import FixedPoint
import random

# Q1.24
qformat = {'signed': True, 'm': 1, 'n': 24}

# A single instance
x = FixedPoint(0, **qformat)

for i in range(10000):
    x.from_string(hex(random.getrandbits(25)))

    # Do stuff with x that doesn't change the Q format. If the Q format
    # is changed, you could use the context manager to restore the original
    # values once the processing in this iteration is done.

overflow

Overflow occurs when a number is greater than the minimum or maximum value that the fixed point number can represent (underflow is also generalized here to overflow). This is determined by the Q format.

The FixedPoint class offers two forms of overflow handling:

  • 'clamp' (default if not specified)

  • 'wrap'

'clamp'

Clamping limits the number to the most maximum or minimum value representable by the available Q format. The overflow_alert property may need to be changed from the default 'error' to allow processing to continue.

>>> x = FixedPoint(999, 0, 4, 1, overflow='clamp')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN1] Integer 999 overflows in UQ4.1 format.

>>> try:
...     x
... except NameError:
...     print('x was not assigned because of the FixedPointOverflowError')
x was not assigned because of the FixedPointOverflowError

>>> x = FixedPoint(999, 0, 4, 1, overflow='clamp', overflow_alert='ignore')
>>> x.clamped, x.qformat, bin(x), float(x)
(True, 'UQ4.1', '0b11111', 15.5)

>>> x = FixedPoint(999, 1, 4, 1, overflow='clamp', overflow_alert='warning')
WARNING [SN3]: Integer 999 overflows in Q4.1 format.
WARNING [SN3]: Clamped to maximum.
>>> x.qformat, hex(x), float(x)
('Q4.1', '0xf', 7.5)

>>> x = FixedPoint(-999, 0, 4, 1, overflow='clamp', overflow_alert='warning')
WARNING [SN4]: Integer -999 overflows in UQ4.1 format.
WARNING [SN4]: Clamped to minimum.
>>> x.qformat, hex(x), float(x)
('UQ4.1', '0x0', 0.0)

>>> x = FixedPoint(-999, 1, 4, 1, overflow='clamp', overflow_alert='ignore')
>>> x.qformat, bin(x), float(x)
('Q4.1', '0b10000', -8.0)

'wrap'

Wrapping will compute the full-length value and then mask away unspecified MSbs. This is also called 2’s complement overflow. The overflow_alert property may need to be changed from the default 'error' to allow processing to continue.

>>> big = 0b11000 # Needs 5 (unsigned) integer bits to represent
>>> big
24
>>> x = FixedPoint(big, 0, 4, 1, overflow='wrap')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN1] Integer 24 overflows in UQ4.1 format.

>>> try:
...     x
... except NameError:
...     print('x was not assigned because of the FixedPointOverflowError')
x was not assigned because of the FixedPointOverflowError

>>> x = FixedPoint(big, 0, 4, 1, overflow='wrap', overflow_alert='ignore')
>>> x.clamped, x.qformat, bin(x), float(x)
(False, 'UQ4.1', '0b10000', 8.0)

>>> x = FixedPoint(big, 1, 4, 1, overflow='wrap', overflow_alert='warning')
WARNING [SN3]: Integer 24 overflows in Q4.1 format.
WARNING [SN3]: Wrapped maximum.
>>> x.qformat, hex(x), float(x)
('Q4.1', '0x10', -8.0)

>>> x = FixedPoint(-1, 0, 4, 1, overflow='wrap', overflow_alert='warning')
WARNING [SN4]: Integer -1 overflows in UQ4.1 format.
WARNING [SN4]: Wrapped minimum.
>>> x.qformat, bin(x), 15.0
('UQ4.1', '0b11110', 15.0)

>>> -big & 0b11111
8
>>> x = FixedPoint(-big, 1, 4, 1, overflow='wrap', overflow_alert='ignore')
>>> x.qformat, bin(x), float(x)
('Q4.1', '0b10000', -8.0)

rounding

Rounding occurs when non-zero fractional bits must be removed from the number. The FixedPoint class offers several forms of rounding:

  • 'convergent' (default for signed numbers if not specified)

  • 'nearest' (default for unsigned numbers if not specified)

  • 'out'

  • 'in'

  • 'up'

  • 'down'

The table below summarizes the behavior of all supported rounding schemes. When the number in the first column is rounded to 0 fractional bits using the rounding scheme in the first row, the result is shown.

Table 2 Rounding Scheme Summary

convergent

nearest

down

in

out

up

-3.49

-3

-3

-4

-3

-3

-3

-3.50

-4

-3

-4

-3

-4

-3

-3.51

-4

-4

-4

-3

-4

-3

+3.49

+3

+3

+3

+3

+3

+4

+3.50

+4

+4

+3

+3

+4

+4

+3.51

+4

+4

+3

+3

+4

+4

'convergent'

Rounds toward the nearest even value in the case of a tie, otherwise rounds to the nearest value. Wikipedia outlines the pros and cons of convergent rounding.

>>> x = FixedPoint(0, signed=1, m=4, n=0, rounding='convergent')
>>> for sign in [+1, -1]:
...     for mag in [3.49, 3.5, 3.51, 4.49, 4.5, 4.51]:
...         x.from_float(init := mag * sign)
...         print(f"{init: .2f} rounds to {x: .0f}")
 3.49 rounds to  3
 3.50 rounds to  4
 3.51 rounds to  4
 4.49 rounds to  4
 4.50 rounds to  4
 4.51 rounds to  5
-3.49 rounds to -3
-3.50 rounds to -4
-3.51 rounds to -4
-4.49 rounds to -4
-4.50 rounds to -4
-4.51 rounds to -5

The example above shows easy-to-understand values and rounding off fractional bits completely. However, the same principal applies to round to a non-zero number of fractional bits:

>>> x = FixedPoint('0b100110', 1, 2, 4, rounding='convergent')
>>> float(x), x.qformat
(-1.625, 'Q2.4')

>>> y = FixedPoint(float(x), n=2, rounding='convergent') # round to 2 frac bits
>>> float(y), bin(y)
(-1.5, '0b1010')

Because convergent rounding can cause the value of the number to increase, it can cause overflow.

>>> x = FixedPoint(3.5)
>>> x.qformat # Can be represented with 2 integer bits
'UQ2.1'
>>> y = FixedPoint(3.5, m=2, n=0, rounding='convergent')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN2] 3.500000e+00 overflows in UQ2.0 format.

>>> z = FixedPoint(3.5, n=0, rounding='convergent') # Requires 4 integer bits
>>> float(z), z.qformat
(4.0, 'UQ3.0')

'nearest'

Rounds toward \(+\infty\) in the case of a tie, otherwise rounds to the nearest value. Wikipedia describes this widely used rounding method.

>>> x = FixedPoint(0, signed=1, m=4, n=0, rounding='nearest')
>>> for sign in [+1, -1]:
...     for mag in [3.49, 3.5, 3.51, 4.49, 4.5, 4.51]:
...         x.from_float(init := mag * sign)
...         print(f"{init: .2f} rounds to {x: .0f}")
 3.49 rounds to  3
 3.50 rounds to  4
 3.51 rounds to  4
 4.49 rounds to  4
 4.50 rounds to  5
 4.51 rounds to  5
-3.49 rounds to -3
-3.50 rounds to -3
-3.51 rounds to -4
-4.49 rounds to -4
-4.50 rounds to -4
-4.51 rounds to -5

The example above shows easy-to-understand values and rounding off fractional bits completely. However, the same principal applies to round to a non-zero number of fractional bits:

>>> x = FixedPoint('0b100110', 1, 2, 4, rounding='nearest')
>>> x.qformat, float(x)
('Q2.4', -1.625)

>>> y = FixedPoint(float(x), n=2, rounding='nearest') # round to 2 frac bits
>>> bin(y), float(y)
('0b1010', -1.5)

Because rounding to nearest can cause the value of the number to increase, it can cause overflow.

>>> FixedPoint(15.5).qformat # Can be represented with 4 integer bits
'UQ4.1'
>>> x = FixedPoint(15.5, m=4, n=0, rounding='nearest')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN2] 1.550000e+01 overflows in UQ4.0 format.

>>> z = FixedPoint(15.5, n=0, rounding='nearest') # Requires 5 integer bits
>>> float(z), z.qformat
(16.0, 'UQ5.0')

'out'

Rounds away from 0 in the case of a tie, otherwise rounds to the nearest value. Wikipedia outlines the pros and cons of rounding away from 0.

>>> x = FixedPoint(0, signed=1, m=4, n=0, rounding='out')
>>> for sign in [+1, -1]:
...     for mag in [3.49, 3.5, 3.51, 4.49, 4.5, 4.51]:
...         x.from_float(init := mag * sign)
...         print(f"{init: .2f} rounds to {x: .0f}")
 3.49 rounds to  3
 3.50 rounds to  4
 3.51 rounds to  4
 4.49 rounds to  4
 4.50 rounds to  5
 4.51 rounds to  5
-3.49 rounds to -3
-3.50 rounds to -4
-3.51 rounds to -4
-4.49 rounds to -4
-4.50 rounds to -5
-4.51 rounds to -5

The example above shows easy-to-understand values and rounding off fractional bits completely. However, the same principal applies to round to a non-zero number of fractional bits:

>>> x = FixedPoint('0b100110', 1, 2, 4, rounding='out')
>>> x.qformat, float(x)
('Q2.4', -1.625)

>>> y = FixedPoint(float(x), n=2, rounding='out') # round to 2 fractional bits
>>> bin(y), float(y)
('0b1001', -1.75)

Because rounding away from 0 causes the magnitude of the number to increase, it can cause overflow.

>>> FixedPoint(15.5).qformat # Can be represented with 4 integer bits
'UQ4.1'
>>> x = FixedPoint(15.5, m=4, n=0, rounding='out')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN2] 1.550000e+01 overflows in UQ4.0 format.

>>> z = FixedPoint(15.5, n=0, rounding='out') # Requires 5 integer bits
>>> float(z), z.qformat
(16.0, 'UQ5.0')

'in'

Rounds unconditionally toward 0.

Note

This has the same effect as truncating decimal digits.

Warning

This is not the same as truncating bits; use the down rounding scheme for bit truncation.

>>> x = FixedPoint(0, signed=1, m=4, n=0, rounding='in')
>>> for sign in [+1, -1]:
...     for mag in [3.49, 3.5, 3.51, 4.49, 4.5, 4.51]:
...         x.from_float(init := mag * sign)
...         print(f"{init: .2f} rounds to {x: .0f}")
 3.49 rounds to  3
 3.50 rounds to  3
 3.51 rounds to  3
 4.49 rounds to  4
 4.50 rounds to  4
 4.51 rounds to  4
-3.49 rounds to -3
-3.50 rounds to -3
-3.51 rounds to -3
-4.49 rounds to -4
-4.50 rounds to -4
-4.51 rounds to -4

The example above shows easy-to-understand values and rounding off fractional bits completely. However, the same principal applies to round to a non-zero number of fractional bits:

>>> x = FixedPoint('0b100110', 0, 2, 4, rounding='in')
>>> x.qformat, float(x)
('UQ2.4', 2.375)

>>> y = FixedPoint(float(x), n=2, rounding='in') # round to 2 fractional bits
>>> bin(y), float(y)
('0b1001', 2.25)

Because rounding in will always decrease the number’s magnitude, it cannot cause overflow.

'up'

Rounds unconditionally toward \(+\infty\).

>>> x = FixedPoint(0, signed=1, m=4, n=0, rounding='up')
>>> for sign in [+1, -1]:
...     for mag in [3.49, 3.5, 3.51, 4.49, 4.5, 4.51]:
...         x.from_float(init := mag * sign)
...         print(f"{init: .2f} rounds to {x: .0f}")
 3.49 rounds to  4
 3.50 rounds to  4
 3.51 rounds to  4
 4.49 rounds to  5
 4.50 rounds to  5
 4.51 rounds to  5
-3.49 rounds to -3
-3.50 rounds to -3
-3.51 rounds to -3
-4.49 rounds to -4
-4.50 rounds to -4
-4.51 rounds to -4

The example above shows easy-to-understand values and rounding off fractional bits completely. However, the same principal applies to round to a non-zero number of fractional bits:

>>> x = FixedPoint('0b100110', 0, 2, 4, rounding='up')
>>> x.qformat, float(x)
('UQ2.4', 2.375)

>>> y = FixedPoint(float(x), n=2, rounding='up') # round to 2 fractional bits
>>> bin(y), float(y)
('0b1010', 2.5)

Because rounding up can cause the number’s magnitude to increase, it can cause overflow.

>>> FixedPoint(15.5).qformat # Can be represented with 4 integer bits
'UQ4.1'
>>> x = FixedPoint(15.5, m=4, n=0, rounding='up')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN2] 1.550000e+01 overflows in UQ4.0 format.

>>> z = FixedPoint(15.5, n=0, rounding='up') # Requires 5 integer bits
>>> float(z), z.qformat
(16.0, 'UQ5.0')

'down'

Rounds unconditionally toward \(-\infty\).

Note

This is the same as truncating bits, since fractional bits cannot have negative weight.

Warning

This is not the same as truncating decimal digits; use the in rounding scheme to achieve decimal digit truncation.

>>> x = FixedPoint(0, signed=1, m=4, n=0, rounding='down')
>>> for sign in [+1, -1]:
...     for mag in [3.49, 3.5, 3.51, 4.49, 4.5, 4.51]:
...         x.from_float(init := mag * sign)
...         print(f"{init: .2f} rounds to {x: .0f}")
 3.49 rounds to  3
 3.50 rounds to  3
 3.51 rounds to  3
 4.49 rounds to  4
 4.50 rounds to  4
 4.51 rounds to  4
-3.49 rounds to -4
-3.50 rounds to -4
-3.51 rounds to -4
-4.49 rounds to -5
-4.50 rounds to -5
-4.51 rounds to -5

The example above shows easy-to-understand values and rounding off fractional bits completely. However, the same principal applies to round to a non-zero number of fractional bits:

>>> x = FixedPoint('0b100110', 0, 2, 4, rounding='down')
>>> x.qformat, float(x)
('UQ2.4', 2.375)

>>> y = FixedPoint(float(x), n=2, rounding='down')
>>> bin(y), float(y)
('0b1001', 2.25)

Because rounding down will always make the value of the number smaller in magnitude, it cannot cause overflow.

overflow_alert

The overflow_alert property indicates how you will be notified and if operation should be halted when overflow occurs. You can choose from:

  • 'error' (default if not specified)

  • 'warning'

  • 'ignore'

Overflow can occur in the following scenarios:

Some methods that can cause overflow have an alert argument which can change the notification scheme for the scope of the method. This can be used if overflow is expected.

'error'

In this notification scheme, overflow causes execution to halt and a FixedPointOverflowError is raised.

>>> x = FixedPoint(999, 0, 1, 0, overflow_alert='error')
Traceback (most recent call last):
    ...
fixedpoint.FixedPointOverflowError: [SN1] Integer 999 overflows in UQ1.0 format.

>>> x # Does not exist because of the exception!
Traceback (most recent call last):
    ...
NameError: name 'x' is not defined

'warning'

In this notification scheme, overflow will emit two warnings, and execution will continue. The first warning informs you of the overflow cause, the second warning informs you of the action taken.

>>> x = FixedPoint(999, 0, 1, 0, overflow='clamp', overflow_alert='warning')
WARNING [SN1]: Integer 999 overflows in UQ1.0 format.
WARNING [SN1]: Clamped to maximum.
>>> float(x), x.clamped
(1.0, True)

>>> y = FixedPoint(3, 1, 2, 0, overflow='wrap', overflow_alert='warning')
WARNING [SN2]: Integer 3 overflows in Q2.0 format.
WARNING [SN2]: Wrapped maximum.
>>> float(y), y.clamped
(-1.0, False)

'ignore'

In this notification scheme, overflow is handled silently.

>>> x = FixedPoint(3.75, 0, 2, 1, overflow_alert='ignore')
>>> float(x), x.clamped
(3.5, True)

>>> y = FixedPoint(-3, 1, 2, 10, overflow='wrap', overflow_alert='ignore')
>>> float(y), bin(y)
(1.0, '0b10000000000')

mismatch_alert

The mismatch_alert property indicates how you will be notified and if operation should be halted when any property of 2 combining FixedPoints do not match. You can choose from:

  • 'error'

  • 'warning' (default if not specified)

  • 'ignore'

For instance, what are the properties of c below?

a = FixedPoint(1,
    overflow='wrap',
    rounding='in',
    overflow_alert='error',
    mismatch_alert='warning',
    implicit_cast_alert='ignore',
    str_base=8,
)

b = FixedPoint(3.12345,
    overflow='clamp',
    rounding='convergent',
    overflow_alert='warning',
    mismatch_alert='ignore',
    implicit_cast_alert='error',
    str_base=2,
)

c = a + b

FixedPoint uses the PropertyResolver class to resolve property mismatches for the following operations:

(in case you’re curious, the example above produces the following):

WARNING [SN1]: Non-matching mismatch_alert behaviors ['ignore', 'warning'].
WARNING [SN1]: Using 'warning'.
WARNING [SN1]: Non-matching overflow behaviors ['clamp', 'wrap'].
WARNING [SN1]: Using 'clamp'.
WARNING [SN1]: Non-matching rounding behaviors ['convergent', 'in'].
WARNING [SN1]: Using 'convergent'.
WARNING [SN1]: Non-matching overflow_alert behaviors ['error', 'warning'].
WARNING [SN1]: Using 'error'.
WARNING [SN1]: Non-matching implicit_cast_alert behaviors ['error', 'ignore'].
WARNING [SN1]: Using 'error'.

Tip

You may consider starting out with mismatch_alert set to 'error', just to make sure you understand the nuances of the FixedPoint class.

'error'

In this notification scheme, the first property mismatch encountered will raise a MismatchError exception.

>>> a = FixedPoint(-1, rounding='convergent', mismatch_alert='error')
>>> b = FixedPoint(+1, rounding='nearest', mismatch_alert='error')
>>> c = a + b  
Traceback (most recent call last):
    ...
fixedpoint.MismatchError: [SN1] Non-matching rounding behaviors ['convergent', 'nearest'].

>>> c # Does not exist because of the exception!
Traceback (most recent call last):
    ...
NameError: name 'c' is not defined

If either FixedPoint’s mismatch_alert is 'error', then an exception is thrown. This is even the case if one of the settings is 'ignore' (see the example below). When there are multiple mismatched properties the first mismatch encountered in the resolution order is the culprit.

>>> a.mismatch_alert = 'ignore' # Now mismatch_alert and rounding don't match
>>> d = a + b 
Traceback (most recent call last):
    ...
MismatchError: Non-matching mismatch_alert behaviors ['error', 'ignore'].

>>> d # Does not exist because of the exception!
Traceback (most recent call last):
    ...
NameError: name 'd' is not defined

'warning'

In this notification scheme, two warnings are emitted for each mismatch, but execution will continue. The first warning informs you of the non-matching properties, the second informs you of the resolution.

>>> x = FixedPoint(1492)
>>> y = FixedPoint(x)
>>> y.rounding = 'down'
>>> z = x - y
WARNING [SN1]: Non-matching rounding behaviors ['down', 'nearest'].
WARNING [SN1]: Using 'nearest'.

>>> z.rounding
'nearest'

Warnings are emitted in the property resolution order.

>>> y.overflow = 'wrap'
>>> zz = x + y # Overflow is resolved before rounding
WARNING [SN1]: Non-matching overflow behaviors ['clamp', 'wrap'].
WARNING [SN1]: Using 'clamp'.
WARNING [SN1]: Non-matching rounding behaviors ['down', 'nearest'].
WARNING [SN1]: Using 'nearest'.

>>> zz.rounding, zz.overflow
('nearest', 'clamp')

For augmented arithmetic operations (e.g. +=), mismatches are ignored because a new FixedPoint is not being created.

>>> import sys, io
>>> sys.stderr = io.StringIO() # redirect stderr to a string buffer
>>> x -= y
>>> y *= x
>>> sys.stderr.flush()
>>> sys.stderr.getvalue() # Nothing written to stderr
''
>>> sys.stderr = sys.__stderr__ # Restore stderr

'ignore'

In this notification scheme, property mismatches are resolved silently. The same examples from above are used.

>>> x = FixedPoint(1492, mismatch_alert='ignore')
>>> y = FixedPoint(x)
>>> y.rounding = 'down'
>>> z = x - y
>>> z.rounding
'nearest'

>>> y.overflow = 'wrap'
>>> zz = x + y # Overflow is resolved before rounding
>>> zz.rounding, zz.overflow
('nearest', 'clamp')

implicit_cast_alert

Arithmetic operations allow non-FixedPoints as operands, and in such cases, are cast to a FixedPoint (see the Initializing from other types section) prior to operation. When a value is cast, its value is compared to the previous object, and if it doesn’t exactly match, FixedPoint emits an alert. The implicit_cast_alert property indicates how you will be notified and if operation should be halted when implicit casting introduces error. You can choose from:

  • 'error'

  • 'warning' (default if not specified)

  • 'ignore'

Note

Finding stimulus to actually cause this alert naturally has not been achieved. This notification scheme was unit tested using a patcher. The examples in this section employ the same technique to illustrate functionality.

Imiplicit cast alerts can conceivably be issued for the following operations:

'error'

In this notification scheme, numerical error introduced by implicit casting will raise an ImplicitCastError exception and operation is halted.

>>> a = FixedPoint(3, implicit_cast_alert='error')
>>> x = a + 0.2
Traceback (most recent call last):
    ...
fixedpoint.ImplicitCastError: [SN1] Casting 0.2 to UQ0.53 introduces an error of 5.551115e-17

>>> x # Does not exist because of the exception!
Traceback (most recent call last):
    ...
NameError: name 'x' is not defined

'warning'

In this notification scheme, numerical error introduced by implicit casting will emit a warning, but the operation is still carried out.

>>> a = FixedPoint(1969, implicit_cast_alert='warning')
>>> a -= 0.3
WARNING [SN1]: Casting 0.3 to UQ0.52 introduces an error of 5.551115e-17

>>> print(f"{a:.60f}")
1968.700000000000045474735088646411895751953125000000000000000000

'ignore'

In this notification scheme, numerical error introduced by implicit casting is ignored.

>>> a = FixedPoint(-10.5, implicit_cast_alert='ignore')
>>> x = 0.2 * a
>>> print(f"{x:.60f}")
-2.100000000000000088817841970012523233890533447265625000000000

str_base

When casting a FixedPoint to a str, the bits of the FixedPoint are displayed. Since the bits are stored internally as an int, they are simply converted using bin(), oct(), str, or hex() from the __str__() method. The str_base property indicates the base of the generated string. You can choose from:

  • 16 (default if not specified)

  • 10

  • 8

  • 2

16

Generates a hexadecimal string representative of FixedPoint.bits. The string is sign extended (or 0-padded) to the bit width of the object, and does not include the radix.

>>> x = FixedPoint(0xdeadbeef, 1, 64, 8) # str_base=16 by default
>>> str(x)
'00000000deadbeef00'
>>> str(-x)
'ffffffff2152411100'

10

Generates a decimal string representative of FixedPoint.bits. The string is not sign extended to the bit width of the object, and does not include a radix. This is equivalent to str(FixedPoint(...).bits).

>>> x = FixedPoint(2, 1, 8, str_base=10)
>>> str(x) # no zero-padding occurs
'2'
>>> x.n += 1 # Effectively multiplies the bits by 2
>>> str(x)
'4'
>>> x.n = 0
>>> str(-x) # Never negative
'254'

8

Generates an octal string representative of FixedPoint.bits. The string is sign extended (or 0-padded) to the bit width of the object, and does not include the radix.

>>> x = FixedPoint(1/3, 1, 1, str_base=8)
>>> x.qformat, str(x)
('Q1.54', '0252525252525252525')
>>> str(-x)
'1525252525252525253'

2

Generates a binary string representative of FixedPoint.bits. The string is sign extended (or 0-padded) to the bit width of the object, and does not include the radix.

>>> x = FixedPoint(-1 + 2**-40, str_base=2)
>>> x.qformat, str(x)
('Q1.40', '10000000000000000000000000000000000000001')
>>> str(-x)
'01111111111111111111111111111111111111111'