Q 表示法#
在开始讨论 q_multiply_shift()
之前,先介绍下 Q 格式计数法 表示定点数。
Q 表示法是一种指定二进制定点数格式参数的方法。例如,在 Q 表示法中,由 Q8.8
表示的数字格式意味着此格式中的定点数具有 8 位整数部分和 8 位小数部分。
Q 表示法的定义#
Texas Instruments 版本
Texas Instruments 定义的 Q 表示法,由字母 Q 后跟一对数字 m.n
组成,其中 m
表示用于值的整数部分的位数,n
表示小数位的位数。
默认情况下,该表示法描述了有符号二进制定点格式,未缩放的整数值以补码格式存储,用于大多数二进制处理器中。第一位总是给出值的符号(1 = 负数,0 = 非负数),并且不计入 m
参数。因此,使用的总位数为 1 + m + n
。
例如,Q3.12
表示总位数为 16 位的有符号二进制定点数,包括符号位、三位整数部分和 12 位小数部分。也就是说,隐式乘以缩放因子 \(2^{-12}\) 的 16 位有符号(补码)整数。
特别是,当 n
为零时,数字只是整数。如果 m
为零,除符号位外的所有位都是小数位;则存储数字的范围是从 \([-1.0, +1.0)\)。
m
和小数点可以省略,在这种情况下,它们会根据存储值的变量或寄存器的大小的推断得出。因此,Q12
表示具有任意位数的有符号整数,隐式地乘以 \(2^{-12}\)。
字母 U
可以添加到 Q
前面,表示无符号二进制定点格式。例如,UQ1.15
描述为隐含缩放因子为 \(2^{-15}\) 的无符号 16 位整数,其范围 \([0.0, \cfrac{2^{16}-1}{2^{15}})\)
ARM 版本
ARM 使用的 Q 表示法的变体中,m
数包括符号位。例如,16 位有符号整数在 TI 变体中表示为 Q15.0
,但在 ARM 变体中表示为 Q16.0
。
Qm.n
或者 UQm.n
格式的分辨率(连续值之间的差)总是 \(2^{-n}\)。可表示值的范围取决于所使用的符号:
Notation |
Texas Instruments Notation |
ARM Notation |
---|---|---|
Signed Qm.n |
\([-2^m, 2^m - 2^{-n}]\) |
\([-2^{m-1}, 2^{m-1} - 2^{-n}]\) |
Unsigned UQm.n |
\([0, 2^m - 2^{-n}]\) |
\([0, 2^m - 2^{-n}]\) |
例如,Q15.1
格式需要 15+1 = 16
位,分辨率为 \(2^{−1} = 0.5\),并且可表示的值范围从 \(−2^{14} = −16384.0\) 到 \(+2^{14} − 2^{−1} = +16383.5\)。在十六进制中,负值范围从 0x8000
到 0xFFFF
,后面是非负值从 0x0000
到 0x7FFF
。
Q 表示法的数学运算#
Q 表示法是两个整数的比率:分子保存在存储器中,分母 \(d\) 等于 \(2^n\)。
比如,在 Q8
中,分母是 \(2^8 = 256\)。对于 \(1.5\) 等于 \(384/256\),这是将 \(1.5\) 转换为 Q8
数。在这种情况下,可以将 \(1.5\) 视为 \(15/10\),然后将其转换 为Q8 形式。首先,需要找到一个数,使得当将其除以 10 时,结果的分母是 \(2\) 的 \(8\) 次方。这个数就是 \(15 * 2^8 = 15 * 256 = 3840\)。所以,\(15/10 = 3840/1000\)。然后,我们再将结果除以 \(2\),得到 \(384/1000\)。这就是 \(384/256\) 的 Q8
表示形式。
如果要保持 Q 数的基数(\(n\)保持不变),则 Q 数数学运算必须保持分母 \(d\) 不变。以下公式显示了对一般 Q 数 \(N1\) 和 \(N2\) 的数学运算。(如果我们考虑上述示例,则 \(N1\) 为 \(384\),\(d\) 为 \(256\)。)
由于分母是 \(2\) 的幂,乘法可以作为算术左移实现,除法可以作为算术右移实现;在许多处理器上,移位比乘法和除法更快。
为了保持准确性,中间的乘法和除法结果必须是双精度的,并且在转换回所需的 Q 数之前必须小心处理四舍五入中间结果。
两个不同 Q 格式基底的数字也可以相乘除,相乘除的数字也可以用另一个基底表示。以下是二个 Q 格式数字 \(N1\)(分母\(d_1\)) 和 \(N2\)(分母\(d_2\)) 的运算,运算结果的分母是 \(d_{3}\):
若用 C 语言,相同 Q 格式基底数字四则运算对应的程式如下(以下的 Q 是表示小数部分的位元数):
Q 数加法#
int16_t q_add(int16_t a, int16_t b)
{
return a + b;
}
有饱和:
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;
}
浮点数有 ±Inf,但 Q 格式没有,若不进行饱和处理,二个很大的正数相加,可能会变成很大的负数。若是用组合语言,可以用 Signed Overflow 旗标来避免 C 语言实现时需要的型态转换。
Q 数减法#
int16_t q_sub(int16_t a, int16_t b)
{
return a - b;
}
Q 数乘法#
// precomputed value:
int Q = 8;
int K = 1 << (Q - 1);
// 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;
}
Q 数除法#
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);
}
int16_t q_max = 0x7fff;
int16_t q_min = 0x8000;
float f_max = 0;
float f_min = 0;
for (int8_t i=15; i>=0; i--) {
f_max = (float)q_max /pow(2, i);
f_min = (float)q_min /pow(2, i);
printf("\t| Q%d | Q%d.%d %f | %f |\r\n",
i, (15-i), i, f_max, f_min);
}
| Q15 | Q0.15 0.999969 | -1.000000 |
| Q14 | Q1.14 1.999939 | -2.000000 |
| Q13 | Q2.13 3.999878 | -4.000000 |
| Q12 | Q3.12 7.999756 | -8.000000 |
| Q11 | Q4.11 15.999512 | -16.000000 |
| Q10 | Q5.10 31.999023 | -32.000000 |
| Q9 | Q6.9 63.998047 | -64.000000 |
| Q8 | Q7.8 127.996094 | -128.000000 |
| Q7 | Q8.7 255.992188 | -256.000000 |
| Q6 | Q9.6 511.984375 | -512.000000 |
| Q5 | Q10.5 1023.968750 | -1024.000000 |
| Q4 | Q11.4 2047.937500 | -2048.000000 |
| Q3 | Q12.3 4095.875000 | -4096.000000 |
| Q2 | Q13.2 8191.750000 | -8192.000000 |
| Q1 | Q14.1 16383.500000 | -16384.000000 |
| Q0 | Q15.0 32767.000000 | -32768.000000 |
// 《Fast Inverse Square Root》
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
#ifndef Q3_VM
#ifdef __linux__
assert( !isnan(y) ); // bk010122 - FPE?
#endif
#endif
return y;
}