Python Fixed-Point Arithmetic API¶
The doppler.arith module is the saturating fixed-point toolkit: elementwise
Q15 (1.15, int16) and Q8 (1.7, int8) add / subtract / multiply, fractional
dot products, saturating shifts, and small running accumulators. Every operation
saturates on overflow rather than wrapping, matching how fixed-point DSP
hardware and the rest of doppler's Q-format paths behave.
Source:
src/doppler/arith/__init__.py
Fixed-point formats¶
| Format | Storage | Range | Resolution | One-liner |
|---|---|---|---|---|
| Q15 | int16 |
[-1, +1) |
2⁻¹⁵ |
1.0 → 32767, -1.0 → -32768 |
| Q8 | int8 |
[-1, +1) |
2⁻⁷ |
1.0 → 127, -1.0 → -128 |
A Q15 value v represents the real number v / 32768. Multiplication is the
fractional product ((a·b) >> 15), so 0.5 × 0.5 = 0.25; addition saturates so
0.5 + 0.5 reads the codec ceiling 32767 (≈ +1.0), never wrapping to a
negative.
Examples¶
Saturating Q15 elementwise ops¶
import numpy as np
from doppler.arith import add_q15, mul_q15
a = np.array([16384, 16384, -32768], dtype=np.int16) # 0.5, 0.5, -1.0
b = np.array([16384, 32767, 16384], dtype=np.int16) # 0.5, ~1.0, 0.5
add_q15(a, b) # array([32767, 32767, -16384]) -> 0.5+0.5 saturates to +1
mul_q15(a, b) # array([ 8192, 16384, -16384]) -> 0.5*0.5 = 0.25
Fractional dot product¶
dot_q15 accumulates the Q15 products in a wide integer and returns a Python
int, so a long inner product never overflows mid-sum.
from doppler.arith import dot_q15
taps = np.array([ 8192, 8192, 8192, 8192], dtype=np.int16) # 0.25 each
sample = np.array([32767, 16384, -16384, 0], dtype=np.int16)
acc = dot_q15(taps, sample) # wide integer accumulator (no overflow)
Saturating shifts¶
from doppler.arith import shl_q15, shr_q15
x = np.array([16384, -32768], dtype=np.int16) # 0.5, -1.0
shl_q15(x, 1) # array([32767, -32768]) -> 0.5<<1 saturates to +1, -1<<1 to -1
shr_q15(x, 2) # array([ 4096, -8192]) -> arithmetic right shift
Running Q15 / Q8 accumulators¶
AccQ15 / AccQ8 keep a 64-bit running sum across calls — drive them
sample-by-sample (step), in bulk (steps), or as a multiply-accumulate
(madd), then read with get() (peek) or dump() (read-and-reset). The wide
register means a long stream cannot overflow.
from doppler.arith import AccQ15
acc = AccQ15()
acc.steps(np.array([8192, 8192, 8192], dtype=np.int16)) # accumulate a block
acc.madd(np.array([16384], dtype=np.int16), # acc += sum(a*b)
np.array([16384], dtype=np.int16))
running = acc.get() # peek without resetting
final = acc.dump() # read and reset to zero
Accumulators¶
AccQ15
¶
Allocate and initialise an AccQ15 accumulator. The accumulator starts at the supplied initial value and may be driven sample-by-sample (step), in bulk (steps), or via multiply-accumulate (madd). The internal register is a 64-bit signed integer so it will not overflow in any realistic DSP workload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
acc
|
int
|
acc state variable. |
0
|
Examples:
Create with defaults:
Reset restores defaults:
reset
¶
Reset the accumulator to zero, mirroring the post-create state. Does not re-initialise to the constructor's acc value — always resets to zero, matching the default initial state for a clean sweep.
Examples:
step
¶
Accumulate one Q15 sample into the running total. The sample is sign-extended to 64 bits before addition, ensuring that negative samples subtract correctly from the accumulator without wrap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
int
|
Q15 input sample (int16_t, range |
required |
Examples:
steps
¶
Accumulate a contiguous block of Q15 samples. Equivalent to calling step() n times but faster for large arrays because the loop can be auto-vectorised by the compiler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
NDArray[int16]
|
Input. |
required |
Examples:
get
¶
dump
¶
Return the accumulated value and reset to zero.
Returns:
| Type | Description |
|---|---|
int
|
Accumulator value before the reset (int64_t). |
Examples:
madd
¶
Multiply-accumulate: acc += sum(a[i] * b[i]) for i in [0, len(a)). Uses AVX2 when available.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
First input array (int16_t). |
required |
b
|
NDArray[int16]
|
Second input array (int16_t), same length as a. |
required |
Examples:
AccQ8
¶
Allocate and initialise an AccQ8 accumulator. The accumulator starts at the supplied initial value and accepts Q8 (int8_t) samples via step(), steps(), or madd(). The 32-bit internal register handles up to roughly 16 million max-magnitude samples before wrap — sufficient for all standard DSP block sizes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
acc
|
int
|
acc state variable. |
0
|
Examples:
Create with defaults:
Reset restores defaults:
reset
¶
Reset the accumulator to zero, mirroring the post-create state. Always resets to zero regardless of the original constructor value, so it is safe to call at the start of any new accumulation window.
Examples:
step
¶
Accumulate one Q8 sample into the running total. The sample is sign-extended to 32 bits before addition so negative samples correctly subtract from the accumulator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
int
|
Q8 input sample (int8_t, range |
required |
Examples:
steps
¶
Accumulate a contiguous block of Q8 samples. Equivalent to calling step() n times; the single loop is more amenable to auto-vectorisation than repeated method calls.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
NDArray[int8]
|
Input. |
required |
Examples:
get
¶
dump
¶
Return the accumulated value and reset to zero.
Returns:
| Type | Description |
|---|---|
int
|
Accumulator value before the reset (int32_t). |
Examples:
madd
¶
Multiply-accumulate: acc += sum(a[i] * b[i]) for i in [0, len(a)).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
First input array (int8_t). |
required |
b
|
NDArray[int8]
|
Second input array (int8_t), same length as a. |
required |
Examples:
Q15 operations (1.15, int16)¶
add_q15
¶
Elementwise saturating two's complement add of two Q15 arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
First input array (int16_t). |
required |
b
|
NDArray[int16]
|
Second input array (int16_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int16]
|
Output. |
Examples:
sub_q15
¶
Elementwise saturating two's complement subtract of two Q15 arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
Minuend array (int16_t). |
required |
b
|
NDArray[int16]
|
Subtrahend array (int16_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int16]
|
Output. |
Examples:
mul_q15
¶
Elementwise Q15 multiply with round-half-up: out[i] = sat16((a[i]*b[i] + 16384) >> 15).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
First input array (int16_t). |
required |
b
|
NDArray[int16]
|
Second input array (int16_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int16]
|
Output. |
Examples:
dot_q15
¶
Inner product of two Q15 arrays. Returns the raw Q30 accumulation as int64_t. Shift right 15 to get a Q15 scalar.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
First input array (int16_t). |
required |
b
|
NDArray[int16]
|
Second input array (int16_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Raw Q30 accumulation (int64_t). |
Examples:
shl_q15
¶
Elementwise arithmetic left shift of a Q15 array with saturation. Equivalent to multiplying by 2^n in fixed-point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
Input array (int16_t). |
required |
n
|
int
|
Shift count (non-negative integer). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int16]
|
Output. |
Examples:
shr_q15
¶
Elementwise arithmetic right shift of a Q15 array with round-half-up. Equivalent to dividing by 2^n.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int16]
|
Input array (int16_t). |
required |
n
|
int
|
Shift count (non-negative integer). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int16]
|
Output. |
Examples:
Q8 operations (1.7, int8)¶
add_q8
¶
Elementwise saturating two's complement add of two Q8 arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
First input array (int8_t). |
required |
b
|
NDArray[int8]
|
Second input array (int8_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int8]
|
Output. |
Examples:
sub_q8
¶
Elementwise saturating two's complement subtract of two Q8 arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
Minuend array (int8_t). |
required |
b
|
NDArray[int8]
|
Subtrahend array (int8_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int8]
|
Output. |
Examples:
mul_q8
¶
Elementwise Q8 multiply with round-half-up: out[i] = sat8((a[i]*b[i] + 64) >> 7).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
First input array (int8_t). |
required |
b
|
NDArray[int8]
|
Second input array (int8_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int8]
|
Output. |
Examples:
dot_q8
¶
Inner product of two Q8 arrays. Returns the raw Q14 accumulation as int32_t.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
First input array (int8_t). |
required |
b
|
NDArray[int8]
|
Second input array (int8_t), same length as a. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Raw Q14 accumulation (int32_t). |
Examples:
shl_q8
¶
Elementwise arithmetic left shift of a Q8 array with saturation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
Input array (int8_t). |
required |
n
|
int
|
Shift count (non-negative integer). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int8]
|
Output. |
Examples:
shr_q8
¶
Elementwise arithmetic right shift of a Q8 array with round-half-up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int8]
|
Input array (int8_t). |
required |
n
|
int
|
Shift count (non-negative integer). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int8]
|
Output. |
Examples:
64-bit saturating shifts¶
shl_i64
¶
Elementwise logical left shift of an int64_t array. No saturation (caller ensures no overflow).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int64]
|
Input array (int64_t). |
required |
n
|
int
|
Shift count (non-negative integer; >= 63 yields 0). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int64]
|
Output. |
Examples:
shr_i64
¶
Elementwise arithmetic right shift of an int64_t array with round-half-up. Useful for normalising dot_q15 Q30 results back to Q15.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
NDArray[int64]
|
Input array (int64_t). |
required |
n
|
int
|
Shift count (non-negative integer; >= 63 is clamped to 63). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[int64]
|
Output. |
Examples: