Converting from float to q7_t in ARM Cortex?

Viewed 645

I understand that the fractional type used in ARM Cortex M4 is used for maximizing the precision of a n-bits number, it is a normalized number from -1 to 1-2^-7 in the case of q7_t.

But how is the typical workflow of its usage?, is it required to normalize and denormalize constantly the variables working with?

I found the next example code that converts from single precision float to q7_t, what is the "128" number doing?

float test_float = 3.1416f;
q7_t test_frac = (q7_t)( test_float * 128);

Thank you in advance.

2 Answers

Because the q7_t type only have seven bits to store the fraction, it's suitable to store it in fractions of 128. It simply means that the decimal part of the number is split into 128 equally sized chunks, each which can be represented with the integer values 0 to 127.

So the integer value 0 represents 0.0. The integer value 1 represents 0.0078125 (1 / 128.0). The integer value 2 represents 0.015625 (2 / 128.0). And so on.

Q formats are simply scalings from integers to fractional values.

Let’s consider the bits in byte, numbered from 0 (on the right when the bits are written out) to 7 (on the left). In the common binary notation, bit i represents a value of 2i. The value represented by the eight bits is the sum of the values represented by the set bits, so 01000011 represents 67 (26 + 21 + 20).

When two’s complement is used to support negative numbers, the highest bit, bit 7 in this example, represents the negative of its plain binary value, so −27.

In a “Q” format, the values of these bits are shifted. In a Q7 format, bit i represents 2i−7, except value for the highest bit is negated, so bit 7 is −27−7 = −1. The name may also be written Q0.7. Conceptually, these numbers are tell us where we put a “binary point” (like a decimal point) in the bits to read the value represented. A Q3.4 format has a sign bit, three bits before the binary point, and four bits after the binary point, like 0100.0011, and it represents 4 + 3/16 = 26−4 + 21−4 + 20−4 = 22 + 2−3 + 2−4 = 4 + 1/8 + 1/16 = 4 3/16.

Another way to think of this is that the bits that represent some x in a plain binary (or two’s complement) format represent x/2n in a Qm.n format.

If your compiler had a fully supported q7 type, you could just assign values to it without dealing with how they are represented: q7 x = .25; would initialize x to contain the value 1/4 by putting the bits 00100000 into it.

From your example, it seems you may have a “do it yourself“ Q7 type that is really just an eight-bit signed integer. In that case, you have to do some conversion between the represented values and the Q7 type. Given any eight bits that represent the integer x, they would represent x/128 in the Q7 format, because in the Q7 format, the bit with value 2i in the integer format has value 2i−7 in the Q7 format.

Thus, if you have some float f, and you want to store it in the Q7 format, using q7_t x = f * 128; adjusts f to the corresponding value in the normal scaling for integers. Conversely, x / 128.f takes the integer value represented by x and scales it to what the bits represent in the Q7 format.

Related