Fixed-point arithmetic represents fractional numbers as ordinary integers with an implied binary point at a position the designer chooses. The hardware is just integer adders, multipliers, and shifters, so it is far smaller, faster, and lower power than floating point. The price is that the designer, not the hardware, must track where the binary point is, how many bits each result needs, and what happens on overflow.

14 min read | Intermediate Level

What You'll Learn

  • How Q-format notation describes integer and fractional bits
  • How bit widths and binary points change through addition and multiplication
  • How to round, truncate, and saturate results correctly in hardware
  • What fixed-point operations cost in hardware, and why they are cheaper than floating point

Q-Format: Where the Binary Point Lives

A fixed-point number is an integer that is understood to be scaled by a power of two. Q-format notation records the split. This article uses Qm.n with m integer bits (including the sign bit) and n fraction bits, so the word is m + n bits wide.

Q1.15 bit layout and Q2.30 product
Top: a Q1.15 word, one sign bit and 15 fraction bits. Bottom: multiplying two Q1.15 values gives a 32-bit Q2.30 product; the Q1.15 result is bits [30:15], after rounding and a saturation check.
FormatWidthRangeResolution (1 LSB)Typical use
Q1.1516-1 to +0.999972-15 ≈ 0.0000305Audio samples, filter coefficients
Q1.78-1 to +0.9922-7 ≈ 0.0078Low-precision coefficients
Q8.816-128 to +127.9962-8 ≈ 0.0039Sensor values, control loops
Q16.1632about ±327682-16 ≈ 0.0000153General-purpose fractional math

Two conventions exist. Some documentation counts the sign bit in m (so a 16-bit signed fraction is Q1.15); other documentation leaves it out (the same format is written Q0.15 or simply Q15). Always check which convention a specification uses before interpreting a bit width.

To convert a real value to Q1.15, multiply by 215 and round to an integer:

Converting between real values and Q1.15

   0.5     x 32768 =  16384  -> 0x4000
  -0.25    x 32768 =  -8192  -> 0xE000
   0.7071  x 32768 =  23170  -> 0x5A82   (error < 1 LSB)
   1.0     x 32768 =  32768  -> does NOT fit: largest is 32767 (0.99997)

  real value = stored integer / 2^15

The Designer's Trade-Off: Range vs. Precision

With a fixed word width, moving the binary point trades range for resolution. Every bit moved from the fraction to the integer part doubles the range and halves the precision. Choosing that split for every signal in a datapath is a real design decision, usually made by simulating the algorithm in a high-level model and measuring the worst-case values and the acceptable error.

Choosing a format for a 16-bit signal (pseudo-procedure)

  1. Find the largest magnitude the signal can reach       e.g. 5.3
  2. Integer bits needed (including sign) = ceil(log2(5.3)) + 1 = 4
  3. Remaining bits are fraction bits: 16 - 4 = 12        -> Q4.12
  4. Check resolution: 2^-12 = 0.000244 -- small enough for the algorithm?
       yes -> done
       no  -> widen the word, or accept saturation at a smaller range

Where Is Fixed-Point Used?

Audio codecs, hearing aids, and voice processors run filters in 16- or 24-bit fixed point. Image signal processors in cameras use fixed point for colour correction and scaling. Neural network inference accelerators commonly run 8-bit integer (INT8) arithmetic, where each tensor has a scale factor that plays the role of the binary point; this can cut multiplier area and energy several-fold compared with 16- or 32-bit floating point.

Addition: Align the Points, Add a Guard Bit

Two fixed-point numbers can only be added if their binary points line up. If they do not, shift one of them first. The sum of two values can be up to twice as large as either, so it needs one more integer bit to be exact.

Addition rules

  Qm.n + Qm.n              -> Q(m+1).n     (one extra integer bit)
  Q4.12 + Q2.14            -> align first: shift the Q4.12 value left by 2
                              to make it Q4.14 (18 bits), then add -> Q5.14
  accumulating N values    -> needs about log2(N) extra integer (guard) bits

  Example: sum of 64 products -> 6 guard bits

Worked Examples in Q4.4

The examples below use Q4.4: 8 bits, with 4 integer bits (including the sign) and 4 fraction bits. One LSB is 2-4 = 0.0625, and the range is -8.0 to +7.9375. To read a value, take the 8-bit pattern as a signed integer and divide by 16. The binary point is shown with a dot only to help reading; the hardware just sees 8 bits.

Example 1: 2.75 + 1.5  (both Q4.4, same format: just add)

     2.75  ->  2.75 x 16 =  44  ->  0010.1100
   + 1.5   ->  1.5  x 16 =  24  ->  0001.1000
   -----------------------------------------
     4.25  <-  68 / 16          <-  0100.0100

  The binary points already line up, so a plain integer adder does the job.
Example 2: -1.25 + 3.5  (a negative value, in two's complement)

    -1.25  ->  -20  ->  1110.1100
   + 3.5   ->   56  ->  0011.1000
   ------------------------------
     2.25  <-   36  <-  0010.0100     (carry out of the top bit is discarded)

  Signed fixed-point uses ordinary two's complement, so the same adder
  handles negative values with no special logic.
Example 3: 6.5 + 3.25  (the true result, 9.75, does not fit in Q4.4)

     6.5   ->  104  ->  0110.1000
   + 3.25  ->   52  ->  0011.0100
   ------------------------------
   8-bit result     ->  1001.1100  = -100 / 16 = -6.25   WRONG: wrapped around

  Option A - add a guard bit (Q5.4, 9 bits):
                       0 1001.1100 = 156 / 16 =  9.75   exact

  Option B - keep 8 bits but saturate:
                       0111.1111   = 127 / 16 =  7.9375 (largest Q4.4 value)

Overflow in signed addition is easy to spot: it can only happen when both inputs have the same sign and the result's sign comes out different. Two positive numbers can never correctly add up to a negative one.

Example 4: 2.75 (Q4.4) + 1.390625 (Q2.6)  (different formats: align first)

  a = 2.75 in Q4.4      ->  0010.1100            (44)
  b = 1.390625 in Q2.6  ->    01.011001          (89)

  Step 1 - align the binary points to the larger fraction width (6 bits)
    a: append two zero fraction bits     ->  0010.110000   (Q4.6)
    b: copy the sign bit into two new
       integer bits (sign-extend)        ->  0001.011001   (Q4.6)

  Step 2 - add, with one guard bit (Q5.6, 11 bits)
        0 0010.110000
      + 0 0001.011001
      ---------------
        0 0100.001001   = 265 / 64 = 4.140625   (2.75 + 1.390625 = 4.140625)

  Rule of thumb: pad the FRACTION side with zeros, extend the INTEGER side
  with copies of the sign bit. Neither changes the value.
Example 5: 1.5 - 2.75  (subtraction = add the two's complement)

    1.5   ->   24  ->  0001.1000
    2.75  ->   44  ->  0010.1100
   -2.75  -> invert all bits and add 1 LSB:
              1101.0011 + 0000.0001  ->  1101.0100   (-44)

     0001.1000
   + 1101.0100
   -----------
     1110.1100   = -20 / 16 = -1.25

  The same adder subtracts: invert the second input and add 1 LSB
  through the adder's carry-in.

Inside a Saturating Adder

A hardware adder that clips instead of wrapping needs only a few extra steps around an ordinary adder. Here is what happens to the overflowing values from Example 3:

Saturating Q4.4 adder, step by step (6.5 + 3.25)

  1. Sign-extend both inputs by one bit (wiring only, no gates)
       6.5  : 0 0110.1000
       3.25 : 0 0011.0100

  2. Add with a 9-bit adder (Q5.4 result, cannot overflow)
              0 1001.1100   = 9.75

  3. Look at the top two bits of the 9-bit sum
       00 or 11 -> the value fits in Q4.4: keep the lower 8 bits
       01       -> too large: output the maximum, 0111.1111 (+7.9375)
       10       -> too small: output the minimum, 1000.0000 (-8.0)

     Here the top bits are 01 -> output 0111.1111 = 7.9375

  4. A multiplexer picks: the sum, the maximum, or the minimum
abExact sum9-bit sumTop two bitsSaturated Q4.4 result
2.751.54.250 0100.0100000100.0100 = 4.25
-1.253.52.250 0010.0100000010.0100 = 2.25
6.53.259.750 1001.1100010111.1111 = 7.9375 (clipped)
-6.0-5.0-11.01 0101.0000101000.0000 = -8.0 (clipped)

What this costs in hardware: sign-extending and appending zero bits are just wiring, with no gates at all. The addition is an adder one bit wider than the inputs. Saturation adds a tiny check on the top two bits and an 8-bit multiplexer that selects the sum, the maximum, or the minimum.

Quick Check

Q: In Q4.4, what is 0101.0000 + 0100.1000, and what does an 8-bit adder without a guard bit return?

Show Answer

0101.0000 is 5.0 and 0100.1000 is 4.5, so the true sum is 9.5. That needs the bit pattern 1001.1000, which an 8-bit signed adder reads as -104 / 16 = -6.5. Both inputs are positive but the result is negative, which signals overflow. A guard bit (Q5.4) gives 0 1001.1000 = 9.5, and a saturating adder returns 7.9375.

Multiplication: Widths and Points Add

When two fixed-point numbers are multiplied, the integer bits add and the fraction bits add. Multiplying two Q1.15 values gives a Q2.30 product, 32 bits wide. To store the result back in Q1.15, drop the extra sign bit and the lowest 15 fraction bits - that is, keep bits [30:15].

Multiplication rules

  Qa.b x Qc.d  ->  Q(a+c).(b+d)

  Q1.15 x Q1.15  ->  Q2.30   (32 bits)
  back to Q1.15  ->  take bits [30:15], after rounding

  Only one input pair overflows Q1.15: (-1) x (-1) = +1.0

Round, Then Saturate: A Complete Q1.15 Multiplier

Simply dropping the low bits (truncation) always rounds toward negative infinity, which adds a small negative bias that accumulates in filters. Rounding adds half an output LSB before dropping the bits. Saturation clamps any result that does not fit to the largest or smallest representable value, instead of letting it wrap around.

Q1.15 x Q1.15 -> Q1.15, step by step

  1. Multiply the two 16-bit signed integers      -> 32-bit product, Q2.30
  2. Round: add half of the result's LSB          -> add 2^14 (bit 14) to the product
  3. Keep bits [31:15]                            -> 17 bits, Q2.15 (overflow still visible)
  4. Saturate:
       above +32767 (0x7FFF)  -> output 0x7FFF  (+0.99997)
       below -32768 (0x8000)  -> output 0x8000  (-1.0)
       otherwise              -> output the lower 16 bits

The effect of rounding is easiest to see with small numbers. Multiply 1.1875 by itself in Q4.4:

Rounding vs. truncation: 1.1875 x 1.1875 in Q4.4  (exact answer 1.41015625)

  1.1875 in Q4.4  ->  19  ->  0001.0011
  19 x 19 = 361   ->  Q8.8 product  0000 0001.0110 1001  = 361 / 256 = 1.41015625

  Back to Q4.4: 4 fraction bits must be dropped
    Truncate:  361 >> 4         = 22  ->  0001.0110 = 1.375    (error -0.035)
    Round:     (361 + 8) >> 4   = 23  ->  0001.0111 = 1.4375   (error +0.027)

  Adding 8 (half of the 16 that is being divided away) before dropping the
  bits gives the nearest representable value instead of always rounding down.
a (real)b (real)Exact producty (hex)y (real)
0.5 (4000)0.5 (4000)0.2520000.25
-0.5 (C000)0.25 (2000)-0.125F000-0.125
-1.0 (8000)-1.0 (8000)+1.07FFF+0.99997 (saturated)

What It Costs in Hardware

OperationHardwareCost
Multiply two 16-bit signed valuesArray or Booth multiplier with an adder tree, or a dedicated multiplier block on FPGAsThe largest part of the datapath
Add half an LSB for roundingAn adder (often merged into the multiplier's final adder)Small
Drop low bits, shift by a constant, sign-extendWires only: it is just a choice of which bits to connectZero
Saturation checkA few gates comparing the top bits, plus a multiplexerSmall

A floating-point multiplier needs all of that plus an exponent adder, normalization shifting, and rounding logic; a floating-point adder is even more complex, with operand alignment shifters and leading-zero detection. For fixed-range signal processing, fixed point gets the same useful precision at a fraction of the area and power.

Where Does Saturation Matter?

In audio and control systems, wrap-around overflow is far worse than clipping. A sample that overflows from +0.99 to -1.0 produces a loud click in audio or a violent reversal in a motor controller. Saturating arithmetic turns the same overflow into a mild clip, so DSP datapaths in codecs, motor drives, and radio baseband processors saturate at key points such as accumulator outputs.

Quick Check

Q: A 32-tap FIR filter multiplies Q1.15 samples by Q1.15 coefficients and sums the 32 products. How wide should the accumulator be to avoid overflow before the final rounding?

Show Answer

Each product is 32 bits (Q2.30). Summing 32 of them needs log2(32) = 5 guard bits, so a 37-bit accumulator (Q7.30) is exact. The final result is then rounded and saturated back to Q1.15.

Common Mistakes

Mistake #1: Zero-extending a signed value

When a signed number is widened, the new bits must be copies of the sign bit. Filling them with zeros changes the value of every negative number: -1.25 in Q4.4 (1110.1100) zero-extended to 9 bits becomes 0 1110.1100, which reads as +14.75 instead of -1.25. Always sign-extend signed values, and only zero-extend unsigned ones.

Mistake #2: Shifting a negative number right with zero fill

Dividing by 2 is a right shift by one bit, but for a signed value the bit shifted in at the top must be a copy of the sign bit (an arithmetic shift). Shifting -1.25 (1110.1100) right with a zero fill gives 0111.0110 = +7.375; an arithmetic shift gives 1111.0110 = -0.625, the correct answer.

Mistake #3: Truncating instead of rounding in accumulating filters

Truncation always rounds toward negative infinity, adding a bias of about half an LSB per operation. In a recursive (IIR) filter or a long accumulation this bias builds up into a visible DC offset. Round before discarding bits wherever the error feeds back or accumulates.

Mistake #4: Forgetting the (-1) x (-1) corner case

In Q1.15, -1.0 is representable but +1.0 is not. Multiplying -1.0 by -1.0 overflows, and without saturation the result wraps to -1.0. Tests with random values rarely hit this exact pair, so include it explicitly in verification.

Practice Exercise

Challenge: A Gain Stage With a Q3.13 Gain

A gain block multiplies a Q1.15 audio sample x by a gain g in the range 0 to 7.9, stored as unsigned Q3.13 in 16 bits. The output must be Q1.15, rounded and saturated.

  1. What is the format and width of the full-precision product (treat g as signed by zero-extending it to 17 bits)?
  2. Which product bits form the Q1.15 result?
  3. Compute the output for x = 0.25, g = 2.5, and for x = 0.5, g = 3.0.
Hint

A zero-extended Q3.13 value is Q4.13 as a signed 17-bit number. Integer bits add and fraction bits add.

Solution
1. Q1.15 x Q4.13 = Q5.28, 33 bits wide.

2. Fraction bits 28 -> 15: discard the low 13 bits.
   Keep bits [32:13] as a Q5.15 value (20 bits), then saturate to Q1.15.

3. x = 0.25 -> 8192 (Q1.15),  g = 2.5 -> 20480 (Q3.13)
   product = 8192 x 20480 = 167,772,160   (Q5.28: / 2^28 = 0.625)
   round   : add 2^12 = 4096, then keep bits [32:13] -> 20480
   20480 fits in Q1.15 -> y = 0x5000 = 0.625

   x = 0.5 -> 16384,  g = 3.0 -> 24576
   product = 402,653,184 (Q5.28: = 1.5)
   after rounding and dropping 13 bits -> 49152, which is above 32767
   -> saturate: y = 0x7FFF = +0.99997
  

Summary

In this tutorial, you learned:

  • Fixed-point numbers are integers with an implied binary point, described by Q-format
  • Moving the binary point trades range against resolution for a fixed word width
  • Addition needs aligned binary points and a guard bit; accumulating N values needs about log2(N) guard bits
  • Multiplication adds integer bits and fraction bits: Q1.15 x Q1.15 = Q2.30
  • Round before discarding bits, and saturate instead of wrapping on overflow
  • Constant shifts and sign extension are just wiring; multipliers dominate the cost; saturation is a small check and a mux
  • Sign-extend signed values when widening them, and use arithmetic shifts that copy the sign bit
Did you find what you were looking for?

Quiz

No quiz questions available for this article.

Cron Job Starts