La notación Q es una forma de especificar los parámetros de un formato de número de punto fijo binario . Por ejemplo, en notación Q, el formato numérico denotado por Q8.8
significa que los números de punto fijo en este formato tienen 8 bits para la parte entera y 8 bits para la parte fraccionaria.
Se han utilizado otras notaciones con el mismo propósito .
La notación Q, tal como la define Texas Instruments , [1] consta de la letra Q seguida de un par de números m . n , donde m es el número de bits utilizados para la parte entera del valor y n es el número de bits fraccionarios.
De forma predeterminada, la notación describe el formato de punto fijo binario con signo , y el entero sin escala se almacena en formato de complemento a dos , que se utiliza en la mayoría de los procesadores binarios. El primer bit siempre da el signo del valor (1 = negativo, 0 = no negativo) y no se cuenta en el parámetro m . Por tanto, el número total w de bits utilizados es 1 + m + n .
Por ejemplo, la especificación Q3.12 describe un número binario de punto fijo con signo con a w = 16 bits en total, que comprende el bit de signo, tres bits para la parte entera y 12 bits que son la fracción. Es decir, un entero de 16 bits con signo (complemento a dos), que se multiplica implícitamente por el factor de escala 2 −12.
En particular, cuando n es cero, los números son simplemente números enteros. Si m es cero, todos los bits excepto el bit de signo son bits de fracción; entonces el rango del número almacenado es de −1,0 (inclusive) a +1,0 (exclusivo).
La m y el punto pueden omitirse, en cuyo caso se infieren del tamaño de la variable o registro donde se almacena el valor. Por lo tanto, Q12 significa un entero con signo con cualquier número de bits, que se multiplica implícitamente por 2 −12 .
La letra U puede ir precedida de la Q para indicar un formato de punto fijo binario sin signo . Por ejemplo, UQ1.15 describe valores representados como enteros de 16 bits sin signo con un factor de escala implícito de 2 −15 , que oscila entre 0,0 y (2 16 −1)/2 15 = +1,999969482421875.
ARM ha utilizado una variante de la notación Q. En esta variante, el número m incluye el bit de signo. Por ejemplo, un entero de 16 bits con signo se indicaría Q15.0
en la variante TI, pero Q16.0
en la variante ARM. [2] [3]
La resolución (diferencia entre valores sucesivos) de un Q m . norte o UQ metro . El formato n es siempre 2 − n . El rango de valores representables depende de la notación utilizada:
Por ejemplo, un número de formato Q15.1 requiere 15+1 = 16 bits, tiene una resolución 2 −1 = 0,5 y los valores representables varían de −2 14 = −16384,0 a +2 14 − 2 −1 = +16383,5. En hexadecimal, los valores negativos van de 0x8000 a 0xFFFF seguidos de los no negativos de 0x0000 a 0x7FFF.
Los números Q son una relación de dos números enteros: el numerador se guarda, el denominador es igual a 2 n .
Considere el siguiente ejemplo:
Si se va a mantener la base del número Q ( n permanece constante), las operaciones matemáticas del número Q deben mantener constante el denominador. Las siguientes fórmulas muestran operaciones matemáticas con los números Q generales y . (Si consideramos el ejemplo mencionado anteriormente, es 384 y es 256.)
Debido a que el denominador es una potencia de dos, la multiplicación se puede implementar como un desplazamiento aritmético hacia la izquierda y la división como un desplazamiento aritmético hacia la derecha; en muchos procesadores los cambios son más rápidos que la multiplicación y la división.
Para mantener la precisión, los resultados intermedios de multiplicación y división deben tener doble precisión y se debe tener cuidado al redondear el resultado intermedio antes de volver a convertirlo al número Q deseado.
Usando C las operaciones son (tenga en cuenta que aquí, Q se refiere al número de bits de la parte fraccionaria):
int16_t q_add ( int16_t a , int16_t b ) { retorno a + b ; }
Con saturación
int16_t q_add_sat ( int16_t a , int16_t b ) { int16_t resultado ; int32_t tmp ; tmp = ( int32_t ) a + ( int32_t ) b ; si ( tmp > 0x7FFF ) tmp = 0x7FFF ; si ( tmp < -1 * 0x8000 ) tmp = -1 * 0x8000 ; resultado = ( int16_t ) tmp ; resultado de retorno ; }
A diferencia de ±Inf de punto flotante, los resultados saturados no son pegajosos y se insaturarán al agregar un valor negativo a un valor saturado positivo (0x7FFF) y viceversa en la implementación que se muestra. En lenguaje ensamblador, el indicador de desbordamiento firmado se puede utilizar para evitar los encasillamientos necesarios para esa implementación de C.
int16_t q_sub ( int16_t a , int16_t b ) { retorno a - b ; }
// valor precalculado: #define K (1 << (Q - 1)) // saturar al rango de int16_t int16_t sat16 ( int32_t x ) { if ( x > 0x7FFF ) return 0x7FFF ; de lo contrario, si ( x < -0x8000 ) devuelve -0x8000 ; de lo contrario devolver ( int16_t ) x ; } int16_t q_mul ( int16_t a , int16_t b ) { int16_t resultado ; int32_t temperatura ; temperatura = ( int32_t ) a * ( int32_t ) b ; // el tipo de resultado es el tipo de operando // Redondeo; los valores medios se redondean hacia arriba temp += K ; // Corregir dividiendo por base y saturar resultado resultado = sat16 ( temp >> Q ); resultado de retorno ; }
int16_t q_div ( int16_t a , int16_t b ) { /* multiplicar previamente por la base (aumentar a Q16 para que el resultado esté en formato Q8) */ int32_t temp = ( int32_t ) a << Q ; /* Redondeo: los valores medios se redondean hacia arriba (hacia abajo para valores negativos). */ /* O comparar los bits más significativos, es decir, if (((temp >> 31) & 1) == ((b >> 15) & 1)) */ if (( temp >= 0 && b >= 0 ) || ( temperatura < 0 && b < 0 )) { temperatura += b / 2 ; /* O cambiar 1 bit, es decir, temp += (b >> 1); */ } más { temp -= b / 2 ; /* O cambiar 1 bit, es decir, temp -= (b >> 1); */ } retorno ( int16_t ) ( temp / b ); }