Block Float Point - 背景知识
块浮点(BFP)向量
标准的(IEEE)浮点对象可以存在于标量或向量中。在标量情况下,它由一个单独的IEEE浮点变量表示。例如,在C语言中:
// 单个IEEE浮点变量
float foo;
在向量情况下,它由一个IEEE浮点变量数组表示。例如,在C语言中:
// IEEE浮点变量数组
float foo[20];
标准的浮点值同时包含尾数 和指数 ,因此这样的变量所代表的逻辑值为 。当你拥有一组标准浮点值的向量时,这一组向量中的每个元素都有自己的尾数和指数:。

相比之下,块浮点对象具有一组尾数,它们都共享同一个指数,因此索引 处的元素的逻辑值为。
struct {
// 尾数数组
int32_t mant[20];
// 共享指数
int32_t exp;
} bfp_vect;

头空间(headroom)
在给定指数 的情况下,32位BFP向量可以表示的最大值由最大的尾数 给出,其逻辑值为 ,一个元素能够表示的最小非零值为 。
由于所有元素必须共享同一个指数,为了避免最大幅度值的溢出或饱和,BFP向量的指数受到具有最大(逻辑)值的元素的约束。这样做的缺点在于,当使用BFP向量表示一个较大动态范围时 - 也就是说,最大幅度元素比最小(非零)幅度元素大得多的时候,较小幅度的元素实际上具有较少的精度位数。这会导致精度损失。
举个例子,当一个BFP向量中包含两个值,分别是 和 时,表示这个向量的一种方式是使用指数。
struct {
int32_t mant[2]; // 尾数数组
int32_t exp; // 共享指数
} vect = { { (1<<20), (0xFF >> 10) }, 0 };

在上图中,分数位(以红色文字显示)被丢弃,因为尾数只有32位。然后,在指数为0的情况下,mant[1] 下溢为0。同时,mant[0] 的12个最高有效位都是零。在这种情况下,你可以发现,我们损失了一部分精度,这时,我们需要利用头空间来控制合适的指数。
有符号整数的头空间是多余的领先标志位的数量。它指的是有符号整数可以左移,而不会丢失信息的位数。因为这些位并没有提供额外的信息,所以我们可以通过左移操作来"去掉"这些位,以便将更多的精度用于表示实际的数值。在图中,与头空间对应的位用绿色文字表示。这里,mant[0] 有10位头空间,mant[1] 有32位头空间(这里是10位而不是11位头空间的原因是,在二进制补码表示中,最高有效位用作符号位)。BFP向量的头空间是其每个元素的头空间中的最小值;在这种情况下,为10位。
如果我们从BFP向量的一个尾数中去掉头空间,则所有其他尾数必须移位相同数量的位,并且向量的指数必须相应调整。左移一位相当于将指数减1,因为单个左移一位相当于乘以2。
在这种情况下,如果我们去掉10位头空间并从指数中减去10,我们得到以下结果:
struct {
int32_t mant[2];
int32_t exp;
} vect = { { (1<<30), (0xFF >> 0) }, -10 };

现在,任何元素都没有丢失信息。BFP算术的主要目标之一就是将BFP向量中的头空间保持在必要的最小限度(等价的说法是,使指数尽可能小)。这样可以最大限度地提高向量中元素的有效精度。
请注意,向量的头空间也能告诉你有关向量中最大幅度尾数的大小的信息。这些信息(与指数一起)可以用于确定操作的最大可能输出,而不必查看尾数。
因此,lib_xcore_math 中的BFP向量带有一个字段,用来跟踪它们当前的头空间。BFP API中的函数使用这个属性来决定如何最好地保留精度。