Hello, welcome!
In this short video, we are going to talk about fixed-point numbers.
This video involves some math, and feel free to pause the video if you need more time.
A fixed-point real number has a fixed number of bits for the integer part, and also a fixed
number of bits for the fractional part.
We often use the Q notation to describe fixed-point numbers.
We have UQ for unsigned fixed-point, and Q for signed fixed-point.
Each format has two parameters, m and n, where n is the number of binary bits in the fractional part.
The definition of m in UQ and Q are different.
In unsigned fixed-point UQ, m is the number of binary bits in the integer part.
However, in the signed fixed-point Q, m is the number of binary bits in the integer part
excluding the sign bit.
As with integer two‘s complement notation, the sign bit is the always the leftmost bit.
Pay attention, in some literature, the sign bit is also included in the integer part and
the total number of binary bits is m + n.
Here, we don‘t include the sign bit in the integer part and thus the total number of
bits for Qm.n is m + n + 1.
Let me show you an example to convert a fixed-point UQ5.3 to a floating point.
In this example, this unsigned fixed-point number has 5 integer bits and 3 fractional bits.
We can directly calculate its value by adding different terms of powers of 2.
Bits after the radix point represent negative powers of 2.
We can take a simpler approach to convert it to float.
We simply treat the fixed-point number as it were an integer.
In other words, we remove the radix point.
Then, we divide the integer by 2 to the power of 3.
In general, we divide the integer by 2 to the power of n, for a fixed point with n fractional bits.
Here is another example that converts signed fixed-point Q4.3 to float.
The conversion process is very similar to the previous one.
We can obtain the result by adding different powers of 2, or by treating it as a signed
integer represented in two‘s complement and then dividing the signed integer by 2
to the power of 3.
Now, let me show you how to convert a floating-point number to fixed-point.
Suppose the number of fractional bits is n.
The conversion process takes two steps: first, multiply the floating-point number by 2 to
the power of n; second, round the result to the nearest integer.
For example, suppose we need to convert pi to the fixed-point format UQ4.12.
First, multiply it with 2 to the power of 12, and then round the product to the nearest
integer, convert it to binary, add two zeros at the very beginning to make the total number
of binary bits 16.
This conversion introduces a representation error and it can be calculated in this way.
Here is another example that converts negative pi to the signed fixed-point format Q3.12.
Similarly, it involves two major steps: multiply it with 2 to the power of 12, and then round
the product to the nearest integer.
The final result is represented in two‘s complement.
In the previous slides, we have talked what fixed-point numbers are and how to obtain
fixed-point representation.
However, we have not answered a very important question yet: why do we need fixed-point in
embedded systems?
Compared with fixed-point, floating-point has several advantages.
For example, floating-point can represent a much wider range of numbers.
In addition, with FPU or standard library support, it is easier to program floating-point
operations and thus the software development time is shorter.
Then, why do we still need fixed-point?
There are three important reasons.
The first reason is to reduce the product costs.
Many microcontrollers don‘t have FPU in order to save costs.
And it is too slow to use the run-time software library to make floating-point calculations.
Fortunately, if FPU is not present and the data range is not very large, we recommend
to use fixed-point operations.
This is because fixed-point arithmetic can be efficiently implemented by using standard
integer operations.
The second reason is that, although fixed-point typically has a very limited dynamic range,
fixed-point has more bits for precision compared to floating-point with the same bit length.
Third, fixed-point is helpful to achieve high-performance in many digital signal processing applications.
Fixed-point has been widely used in many video or audio DSP applications.
I will give you more explanation later in this video.
Let me use a simplified 5-bit IEEE 754-like floating-point to illustrate the tradeoff
between the representation range and the precision.
On this axis, all dots are numbers that can be represented by this 5-bit IEEE 754-like format.
As we can see, representable floating-point numbers are denser at the smaller end.
The gap between two consecutive numbers increases as the magnitude gets larger.
The precision is defined as the difference between two neighbor numbers.
For floating-point, the precision decreases as the magnitude increases.
For fixed-point, representable numbers are evenly distributed in the representation range.
Therefore, its precision is a constant.
In general, fixed-point provides higher precision compared with floating-point with the same
number of bits.
Adding or subtracting two fixed-point numbers with the same format can be simply implemented
by using standard integer instructions add or subtract.
We can simply treat them as integers, and add or subtract them directly.
Intuitively, this makes sense.
Since both fixed-point numbers have the same format, the radix points are at the same location
and these two numbers are perfectly aligned.
Therefore, we can directly add or subtract them.
In the previous slide, I have shown that fixed-point addition and subtraction work exactly like
their integer counterpart.
Based on this observation, fixed-point can be used to efficiently speed up DSP applications.
For example, if each fixed-point number is represented using 8 bits, a single register
can hold four fixed-point numbers.
We can use SADD8 or UADD8 to add four pairs of signed or unsigned fixed-point numbers
in one clock cycle.
Such instructions are called Single Instruction Multiple Data (SIMD), which perform the same
operation on multiple data operands concurrently.
Here are two SIMD instructions that add two pairs of 16-bit fixed-point numbers concurrently.
Cortex-M4 supports many other SIMD instructions.
Here I present a few of them to show you the motivation why fixed-point is widely used
in DSP applications.
Now, let‘s see how we multiply two fixed-point numbers.
For simplicity, we assume each fixed-point number has 32 bits totally, with 16 fractional bits.
If we multiply two 32-bit integers, the product can be as large as 64 bits.
However, for fixed-point multiplication, it is often that we need to limit the product
to 32 bits.
Thus, which out of these 64 bits should be our final result?
The answer is that, we should remove the leading 16 bits and also the tailing 16 bits, and
only keep the middle 32 bits as the final result.
Let me explain the math behind it.
For fixed-point numbers fA, fB, and fC, assume their corresponding integer is IA, IB, and
IC, respectively.
The integer is directly obtained by removing the radix point from the fixed-point.
We know that a fixed-point number equals to its corresponding integer divided by 2 to
the power of 16.
Now, let‘s find out the integer value for the fixed-point fc.
The calculation is shown here.
The integer IC equals to the 64-bit product shifted right by 16 bits.
This means that the middle 32 bits of the product is what we want.
Now let‘s see how we can implement the fixed-point multiplication in assembly.
For unsigned fixed-point UQ16.16, we first use the instruction Unsigned Multiply Long
to get the 64-bit product.
In this instruction, register r0 and r1 are two input arguments to this function, and
each of them holds a 32-bit fixed-point number.
The product has 64 bits, with the upper word placed in register r3, and the lower word
in register r2.
Then, we shift left the upper word by 16 bits.
This removes the leading 16 bits of the product.
Next, we shift right the lower word by 16 bits.
This removes the tailing 16 bits of the product.
Finally, we combine the upper and lower word into a single register by using the bit-wise
OR instruction.
The final result is placed in register r0, and it holds the lower 16 bits of the integer
part and the upper 16 bits of the fractional part.
For signed fixed-point multiplication, the process is the same except that we need to
replace the instruction Unsigned Multiply Long with the Signed Multiply Long.
Now, let‘s look at fixed-point division.
Suppose we are dividing fixed-point fA by fB.
Assume that they are in the format of UQ16.16.
Before the division, we need to shift left the 32-bit integer IA by 16 bits.
Because a register can only hold 32 bits, we need two registers to hold the shift result.
In addition, the leading 16 bits are extended by the sign of fA.
Then, we divide the sign-extended 64-bit dividend by the integer IB.
The 32-bit quotient is what we want.
Here is the math behind it.
As discussed previously, a fixed-point number equals to the corresponding integer divided
by 2 to the power 16.
Accordingly, the integer IC can be calculated in this way.
As you can see from the final result here, we need to shift IA left by 16 bits and then
divide it by IB.
Here is the assembly implementation of dividing two 32-bit fixed-point numbers.
These assembly instructions extend both IA and IB into 64-bit signed integers.
In addition, it also shifts IA left by 16 bits.
Because we don‘t have an assembly instruction that makes 64-bit division, we need to use
a software library function to divide two 64-bit integers.
The library implementation is not presented here.
Thank you for watching.
Please visit the book web site for more examples.
See you next time.