Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""B-ASIC quantization module."""
import math
from enum import Enum
from b_asic.types import Num
class Quantization(Enum):
"""Quantization types."""
ROUNDING = 1
"Standard two's complement rounding, i.e, tie rounds towards infinity."
TRUNCATION = 2
"Two's complement truncation, i.e., round towards negative infinity."
MAGNITUDE_TRUNCATION = 3
"Magnitude truncation, i.e., round towards zero."
JAMMING = 4
"Jamming/von Neumann rounding, i.e., set the LSB to one"
UNBIASED_ROUNDING = 5
"Unbiased rounding, i.e., tie rounds towards even."
class Overflow(Enum):
"""Overflow types."""
TWOS_COMPLEMENT = 1
"Two's complement overflow, i.e., remove the more significant bits."
SATURATION = 2
"""
Two's complement saturation, i.e., overflow return the most postive/negative
number.
"""
def quantize(
value: Num,
fractional_bits: int,
integer_bits: int = 1,
quantization: Quantization = Quantization.TRUNCATION,
overflow: Overflow = Overflow.TWOS_COMPLEMENT,
):
r"""
Quantize *value* assuming two's complement representation.
Quantization happens before overflow, so, e.g., rounding may lead to an overflow.
The total number of bits is *fractional_bits* + *integer_bits*. However, there is
no check that this will be a positive number. Note that the sign bit is included in these
bits. If *integer_bits* is not given, then use 1, i.e., the result is between
.. math:: -1 \leq \text{value} \leq 1-2^{-\text{fractional_bits}}
If *value* is a complex number, the real and imaginary parts are quantized separately.
Parameters
----------
value : int, float, complex
The value to be quantized.
fractional_bits : int
Number of fractional bits, can be negative.
integer_bits : int, default: 1
Number of integer bits, can be negative.
quantization : :class:`Quantization`, default: :class:`Quantization.TRUNCATION`
Type of quantization.
overflow : :class:`Overflow`, default: :class:`Overflow.TWOS_COMPLEMENT`
Type of overflow.
Returns
-------
int, float, complex
The quantized value.
Examples
--------
>>> from b_asic.quantization import quantize, Quantization, Overflow
...
... quantize(0.3, 4) # Truncate 0.3 using four fractional bits and one integer bit
0.25
>>> quantize(0.3, 4, quantization=Quantization.ROUNDING) # As above, but round
0.3125
>>> quantize(1.3, 4) # Will overflow
-0.75
>>> quantize(1.3, 4, 2) # Use two integer bits
1.25
>>> quantize(1.3, 4, overflow=Overflow.SATURATION) # use saturation
0.9375
>>> quantize(0.3, 4, -1) # Three bits in total, will overflow
-0.25
"""
if isinstance(value, complex):
return complex(
quantize(
value.real,
fractional_bits=fractional_bits,
integer_bits=integer_bits,
quantization=quantization,
overflow=overflow,
),
quantize(
value.imag,
fractional_bits=fractional_bits,
integer_bits=integer_bits,
quantization=quantization,
overflow=overflow,
),
)
b = 2**fractional_bits
v = b * value
if quantization is Quantization.TRUNCATION:
v = math.floor(v)
elif quantization is Quantization.ROUNDING:
v = math.floor(v + 0.5)
elif quantization is Quantization.MAGNITUDE_TRUNCATION:
if v >= 0:
v = math.floor(v)
else:
v = math.ceil(v)
elif quantization is Quantization.JAMMING:
v = math.floor(v) | 1
else: # Quantization.UNBIASED_ROUNDING
v = round(v)
v = v / b
i = 2 ** (integer_bits - 1)
if overflow is Overflow.SATURATION:
pos_val = i - 1 / b
neg_val = -i
v = max(neg_val, min(v, pos_val))
else: # Overflow.TWOS_COMPLEMENT
v = (v + i) % (2 * i) - i
return v