Q (number format)

Q is a binary fixed point number format where the number of fractional bits (and optionally the number of integer bits) is specified. For example, a Q15 number has 15 fractional bits; a Q1.14 number has 1 integer bit and 14 fractional bits.
For signed fixed-point numbers, there are two conflicting usages of Q format. In one usage, the sign bit is counted as an integer bit, but in the second usage it isn't. For example, a 16-bit signed integer could be denoted Q16.0 or Q15.0. Therefore, it is necessary to also specify the total number of bits (in this case 16) to describe a signed fixed-point number using Q format.
Q format is one of several widely-used fixed-point format notations. Fixed-point numbers are often used in hardware that does not have a floating-point unit and in applications that require constant resolution.
Characteristics.
Q format numbers are notionally fixed point numbers, that is, they are stored and operated upon as regular binary signed integers, thus allowing standard integer hardware/ALU to perform rational number calculations. The number of integer bits, fractional bits and the underlying word size are to be chosen by the programmer on an application-specific basis the programmer's choices of the foregoing will depend on the range and resolution needed for the numbers.
Some DSP architectures offer native support for common formats, such as Q1.15. In this case, the processor can support arithmetic in one step, offering saturation (for addition and subtraction) and renormalization (for multiplication) in a single instruction. Most standard CPUs do not. If the architecture does not directly support the particular fixed point format chosen, the programmer will need to handle saturation and renormalization explicitly with bounds checking and bit shifting.
There are two conflicting notations for fixed point. Both notations are written as Q"m"."n", where:
One convention includes the sign bit in the value of "m", and the other convention does not. The choice of convention can be determined by summing "m+n". If the value is equal to the register size, then the sign bit is included in the value of "m". If it is one less than the register size, the sign bit is not included in the value of "m".
In addition, the letter U can be prefixed to the Q to indicate an unsigned value, such as UQ1.15, indicating values from 0.0 to +1.999969482421875 (that is, formula_1).
Signed Q values are stored in two's complement format, just like signed integer values on most processors. In two's complement, the sign bit is extended to the register size.
For a given Q"m"."n" format, using an "m"+"n" bit signed integer container with "n" fractional bits:
For a given UQ"m"."n" format, using an "m"+"n" bit unsigned integer container with "n" fractional bits:
For example, a Q15.1 format number:
Unlike floating point numbers, the resolution of Q numbers will remain constant over the entire range.
Conversion.
Float to Q.
To convert a number from floating point to Q"m"."n" format:
Q to float.
To convert a number from Q"m"."n" format to floating point:
Math operations.
Q numbers are a ratio of two integers: the numerator is kept in storage, the denominator is equal to 2"n".
Consider the following example:
If the Q number's base is to be maintained ("n" remains constant) the Q number math operations must keep the denominator constant. The following formulas show math operations on the general Q numbers formula_6 and formula_7.
formula_8
Because the denominator is a power of two the multiplication can be implemented as an arithmetic shift to the left and the division as an arithmetic shift to the right; on many processors shifts are faster than multiplication and division.
To maintain accuracy the intermediate multiplication and division results must be double precision and care must be taken in rounding the intermediate result before converting back to the desired Q number.
Using C the operations are (note that here, Q refers to the fractional part's number of bits) :
Addition.
int16_t q_add(int16_t a, int16_t b)
 return a + b;
With saturation
int16_t q_add_sat(int16_t a, int16_t b)
 int16_t result;
 int32_t tmp;
 tmp = (int32_t)a + (int32_t)b;
 if (tmp > 0x7FFF)
 tmp = 0x7FFF;
 if (tmp < -1 * 0x8000)
 tmp = -1 * 0x8000;
 result = (int16_t)tmp;
 return result;
Unlike floating point ±Inf, saturated results are not sticky and will unsaturate on adding a negative value to a positive saturated value (0x7FFF) and vice versa in that implementation shown. In assembly language, the Signed Overflow flag can be used to avoid the typecasts needed for that C implementation.
Subtraction.
int16_t q_sub(int16_t a, int16_t b)
 return a - b;
Multiplication.
// precomputed value:
// saturate to range of int16_t
int16_t sat16(int32_t x)
 if (x > 0x7FFF) return 0x7FFF;
 else if (x < -0x8000) return -0x8000;
 else return (int16_t)x;
int16_t q_mul(int16_t a, int16_t b)
 int16_t result;
 int32_t temp;
 temp = (int32_t)a * (int32_t)b; // result type is operand's type
 // Rounding; mid values are rounded up
 temp += K;
 // Correct by dividing by base and saturate result
 result = sat16(temp » Q);
 return result;
Division.
int16_t q_div(int16_t a, int16_t b)
 /* pre-multiply by the base (Upscale to Q16 so that the result will be in Q8 format) */
 int32_t temp = (int32_t)a « Q;
 /* Rounding: mid values are rounded up (down for negative values). */
 /* OR compare most significant bits i.e. if (((temp » 31) & 1) == ((b » 15) & 1)) */
 if ((temp >= 0 && b >= 0) || (temp < 0 && b < 0)) { 
 temp += b / 2; /* OR shift 1 bit i.e. temp += (b » 1); */
 } else {
 temp -= b / 2; /* OR shift 1 bit i.e. temp -= (b » 1); */
 return (int16_t)(temp / b);