Skip to main content
欢迎来到PAWPAW技术文档网站了解更多信息

定点数-背景知识

请注意,buff[]是一个q1_31数组。q1_31是在lib_xcore_math中定义的32位整数类型。在处理32位定点算术时,我们可以始终使用int32_t值,但这并不能告诉我们数据所表示的逻辑值。由于与定点值关联的指数是固定的,我们可以直接将这些信息编码到存储它们的变量和常量的类型中。

为此,lib_xcore_math定义了q1_31和许多相关类型(例如q2_30q8_24等),用于表示底层数据的_Q格式_(Q格式)。它们可以用来向用户_暗示_给定值应与特定指数相关联。同样,它们可以被理解为包含特定数量的小数位。

类型q1_31对应于Q格式Q1.31。这是一个带有1个整数位和31个小数位的32位值。同样,q8_24表示Q8.24格式,具有8个整数位和24个小数位,依此类推。

  • 尾数和指数
  • 定点算术
  • lib_xcore_math中的定点支持

组件函数

第1部分类似,第2部分中每个阶段的行为由4个组件函数定义:

  • rx_frame()
  • tx_frame()
  • filter_sample()
  • filter_task()

这些函数在第2部分中的作用与在第1部分中的作用基本相同。

其中,filter_task()rx_frame()tx_frame()这三个函数在所有三个阶段中使用相同的实现。在这些阶段之间唯一变化的是filter_sample()的实现。

在本页面中,我们将先看看每个阶段共有的这些函数,然后深入研究选择指数来表示我们的定点值的逻辑。最后,我们将给出一些深入的例子。

第2部分滤波器系数

第1部分不同的是,滤波器系数在第1A中被实现为一个常量double数组(第1部分)或float数组(阶段1阶段2),而在第2部分中,滤波器系数由一个q4_28值数组表示(即Q4.28格式)。这些系数具有28个小数位,因此其隐含指数为-28

本部分中的滤波器系数来自filter_coef_q4_28.c

作为一个平均FIR滤波器,所有系数的逻辑值都是11024=0.0009765625\frac{1}{1024} = 0.0009765625。让我们将其转换为Q4.28格式。

x=11024y^=28y2y^=xy228=210y=210228y=218=262144=0x40000\begin{aligned} x &= \frac{1}{1024} \\ \hat{y} &= -28 \\ \\ \mathtt{y}\cdot 2^{\hat{y}} &= x \\ \mathtt{y}\cdot 2^{-28} &= 2^{-10} \\ \mathtt{y} &= 2^{-10}\cdot 2^{28} \\ \mathtt{y} &= 2^{18} = 262144 = \mathtt{0x40000} \end{aligned}

因此,在Q4.28格式中,我们的每个滤波器系数应该由整数值0x40000表示。

使用Q4.28格式是我们的选择。通常,在选择定点数据的表示时,最好使用最低的指数来避免数据丢失。这对应于具有零头空间的概念,我们将在第3部分中讨论。使用最小指数通常意味着最大精度。在这种特殊情况下,因为我们的系数是2的幂,任何允许的指数都是等效的。

定点和浮点值的科学计数法

我们需要将我们的概念上的数学对象(如滤波器系数)表示为代码中的实际对象。在第1部分中,这很简单,因为标量浮点类型是一种封装了我们关心的所有部分的抽象。因此,我们不需要考虑单个值或向量的尾数和指数。

从本部分开始,我们将发现我们需要考虑这些概念上的数学对象(如实数)并进行推理。我们需要能够在代码中抽象地表示和操作它们,同时也要在代码中具体地表示和操作它们。

为此,我们引入了一种新的记法,这种记法将在本教程的其余部分中使用。

假设xx是一个实数值,即一个抽象的、_数学_的对象,需要使用整数来明确表示它(因为_概念上_的对象不能出现在_实际_的代码中)。为此,它需要一个尾数和一个指数。这本质上与科学计数法相同,只是使用2的幂而不是10的幂。

我们不会选择任意的新字母来代表每个部分,否则很快就会变得混乱不堪。因此,我们将采用一种约定,其中x\mathtt{x}(等宽字体)表示尾数,x^\hat{x}表示其指数。xx仍然表示我们所指的逻辑值——那就是我们希望表示的值。

xx2x^ x \mapsto \mathtt{x}\cdot 2^{\hat{x}}

这种记法旨在减少对方程中符号之间关系的混淆。

请注意,这些并不严格地_相等_。如果xx13\frac13,那么我们用x\mathtt{x}x^\hat{x}表示xx时,将会有舍入误差。然而,通常在呈现方程时,这个细节会被忽略。

此外,为了避免混淆,我们将采用一种约定,其中_概念上_的对象的向量将使用_下标_来表示给定索引处的元素,例如xkx_k,而_尾数_的向量将使用_方括号_来表示给定索引处的元素,例如x[k]\mathtt{x}[k]。这也意味着我们从抽象数学向具体代码的转变。

一个例子(标量)

为了使其具体化,考虑逻辑值p=6.03125p = 6.03125。展开pp,我们有

p=6.03125p2p^=6.03125\begin{aligned} p &= 6.03125 \\ \mathtt{p} \cdot 2^{\hat{p}} &= 6.03125 \\ \end{aligned}

假设p\mathtt{p}必须存储在一个int32_t中,那么p\mathtt{p}p^\hat{p}是多少?我们有几个选项。

首先,我们应该始终考虑p^\hat{p}的_下限_。问题是,什么是允许我们不能表示pp的最小p^\hat{p}?为了弄清楚这一点,我们需要知道在哪些情况下我们_不能_表示pp

p=p2p^p2p^=p\begin{aligned} p &= \mathtt{p} \cdot 2^{\hat{p}} \\ p \cdot 2^{-\hat{p}} &= \mathtt{p} \\ \end{aligned}

int32_t表示法为我们提供了对p\mathtt{p}的边界

INT32_MINpINT32_MAX231p2311231p<231\begin{aligned} \mathtt{INT32\_MIN} \le &\mathtt{p} \le \mathtt{INT32\_MAX} \\ -2^{31} \le &\mathtt{p} \le 2^{31} - 1 \\ -2^{31} \le &\mathtt{p} < 2^{31} \\ \end{aligned}

因此,任何使p\mathtt{p}大于或等于2312^{31}p^\hat{p}值都是不允许的。

p231p2p^231p2p^p231p2p^231plog2(2p^)log2(231p)p^log2(231)log2(p)p^31log2(6.03125)p^312.59246p^28.40754p^28.40754\begin{aligned} \mathtt{p} \ge 2^{31} \\ p \cdot 2^{-\hat{p}} \ge 2^{31} \\ \frac{p \cdot 2^{-\hat{p}}}{p} \ge \frac{2^{31}}{p} \\ 2^{-\hat{p}} \ge \frac{2^{31}}{p} \\ log_2(2^{-\hat{p}}) \ge log_2\left(\frac{2^{31}}{p}\right) \\ -\hat{p} \ge log_2(2^{31}) - log_2(p) \\ -\hat{p} \ge 31 - log_2(6.03125) \\ -\hat{p} \ge 31 - 2.59246 \\ -\hat{p} \ge 28.40754 \\ \hat{p} \le -28.40754 \\ \end{aligned}

这告诉我们,p^\hat{p}值小于或等于约为28.4075-28.4075将导致p\mathtt{p}(我们的int32_t值)溢出。请注意,p^\hat{p}必须是一个_整数_,因此该条件实际上变为p^<28\hat{p} < -28

让我们来验证一下。首先,让我们验证p^=28\hat{p}=-28是否可行。

p^=28p=p2p^p=6.03125228p=1619001344.0=0x60800000\begin{aligned} \hat{p} &= -28 \\ \\ \mathtt{p} &= p \cdot 2^{-\hat{p}} \\ \mathtt{p} &= 6.03125 \cdot 2^{28} \\ \mathtt{p} &= 1619001344.0 = \mathtt{0x60800000} \\ \end{aligned}

0x60800000小于INT32_MAX,所以这是可行的。那么p^=29\hat{p}=-29呢?

p^=29p=p2p^p=6.03125229p=1619001344.0=0xC1000000\begin{aligned} \hat{p} &= -29 \\ \\ \mathtt{p} &= p \cdot 2^{-\hat{p}} \\ \mathtt{p} &= 6.03125 \cdot 2^{29} \\ \mathtt{p} &= 1619001344.0 = \mathtt{0xC1000000} \\ \end{aligned}

0xC1000000大于INT32_MAX,所以我们确认29-29不是一个可接受的指数。

因此,p^\hat{p}的_最小_值是28-28

是否有一个p^\hat{p}的_最大_值?是和否——主要是否。

当减小p^\hat{p}时,我们观察到在28-2829-29之间突然截断,我们立即无法表示pp。在另一个方向上的效果更加微妙。

尝试使用有限信息表示实数通常会产生舍入误差。特别是,由尾数和指数(x2x^)(\mathtt{x}\cdot 2^{\hat{x}})表示的对象xx的分辨率为2x^2^{\hat{x}}。要理解为什么,考虑将整数x\mathtt{x}加上或减去11的效果(注意,这与x\mathtt{x}的位深度无关)。

(x±1)2x^=(x2x^)±(12x^)=x±2x^\begin{aligned} & (\mathtt{x}\pm 1) \cdot 2^{\hat{x}} \\ &= (\mathtt{x}\cdot 2^{\hat{x}}) \pm (1 \cdot 2^{\hat{x}}) \\ &= x \pm 2^{\hat{x}} \end{aligned}

这也意味着使用指数x^\hat{x}表示的可表示的具有最小幅度的非零值是±2x^\pm 2^{\hat{x}}。鉴于这些情况,应该清楚,指数(x^\hat{x})越大,分辨率越低,因此量化误差越大。

回到我们的例子p=6.03125p = 6.03125,回想一下,对于p^=28\hat{p} = -28p\mathtt{p}的值是0x60800000。请注意,该值的最低有效位是0。对于每个p^\hat{p}的增加,p\mathtt{p}的值必须向右移动1位。但是移出的零实际上不会影响我们表示的值。

因此,如果我们选择p^=5\hat{p} = -5

p^=5p=p2p^p=6.0312525p=193.0=0x000000C1\begin{aligned} \hat{p} &= -5 \\ \\ \mathtt{p} &= p \cdot 2^{-\hat{p}} \\ \mathtt{p} &= 6.03125 \cdot 2^{5} \\ \mathtt{p} &= 193.0 = \mathtt{0x000000C1} \\ \end{aligned}

然后将其反转以计算pp的值,我们得到19325=6.03125193 \cdot 2^{-5} = 6.03125——我们准确地表示了pp

但是,如果我们选择p^=4\hat{p} = -4

p^=4p=p2p^p=6.0312524p=96.5\begin{aligned} \hat{p} &= -4 \\ \\ \mathtt{p} &= p \cdot 2^{-\hat{p}} \\ \mathtt{p} &= 6.03125 \cdot 2^{4} \\ \mathtt{p} &= 96.5 \\ \end{aligned}

这里我们被迫将96.596.5向上或向下舍入,因为p\mathtt{p}是一个整数。让我们看看它的结果...

p^=49624=6.09724=6.0625(9724)(9624)=0.0625=2p^\begin{aligned} \hat{p} &= -4 \\ \\ 96 \cdot 2^{-4} &= 6.0 \\ 97 \cdot 2^{-4} &= 6.0625 \\ \\ (97 \cdot 2^{-4}) - (96 \cdot 2^{-4}) &= 0.0625 \\ &= 2^{\hat{p}} \end{aligned}

在这里,我们可以看到使用p^=4\hat{p}=-4开始引入量化误差,再次发现分辨率直接由指数给出。

总之,对于我们的示例p=6.03125p = 6.03125,只要

28p^5 -28 \le \hat{p} \le -5

我们就可以准确地表示pp。低于该范围会立即破坏我们的值,而高于该范围会通过量化误差逐渐破坏它。

一个例子(向量)

如果我们有pˉ\bar{p}而不是pp——也就是说,如果我们需要为向量选择指数p^\hat{p},而不是标量?在浮点数中,pˉ\bar{p}的每个元素pkp_k都有自己的指数,与pˉ\bar{p}中的其他元素无关。

对于定点或块浮点数(BFP),我们为整个向量选择一个单独的指数。两者之间的区别在于,在定点中,指数在_编译时_固定,不依赖于实际数据(通常是隐含的而不是显式的),而在BFP中,指数在运行时根据数据动态选择(通常)并且必须明确表示。

考虑向量pˉ=[0.005432,1.2245,0.5,3.2]\bar{p} = [0.005432, 1.2245, -0.5, -3.2]。我们如何为这个向量选择指数p^\hat{p}

事实证明,即使对于定点和BFP,选择向量的指数的逻辑最终归结为标量情况的逻辑:

  • 我们仍然需要避免尾数溢出
  • 我们仍然希望尽可能少的量化误差

事实上,向量的最大幅度元素将决定指数。每个元素都会暗示其自己关于p^\hat{p}的下限,但具有最大幅度的元素将具有_最高_的下限。请注意,最大_幅度_元素可以是正数也可以是负数。

在我们的例子中,我们将最大幅度元素称为pp'

p=pkk=argmaxnpnk=3p=p3p=3.2\begin{aligned} p' &= p_{k} \\ k &= \operatorname{argmax}_n |p_n| \\ k &= 3 \\ p' &= p_3 \\ p' &= -3.2 \\ \end{aligned}

这次pp'是负数。这仍然遵循与之前相同的逻辑,其中

INT32_MINpINT32_MAX \mathtt{INT32\_MIN} \le \mathtt{p}' \le \mathtt{INT32\_MAX}

只不过,这次我们担心p\mathtt{p}'的值过分地偏向负数,因此我们需要在相反的方向设定一个边界。

p<INT32_MINp<231p2p^<231p2p^p>231p(除以负数)2p^>671088640.0log2(2p^)>log2(671088640.0)p^>29.32192p^<29.32192\begin{aligned} \mathtt{p}' < \mathtt{INT32\_MIN} \\ \mathtt{p}' < -2^{31} \\ p' \cdot 2^{-\hat{p}} < -2^{31} \\ \frac{p' \cdot 2^{-\hat{p}}}{p'} > \frac{-2^{31}}{p'} && \text{(除以负数)}\\ 2^{-\hat{p}} > 671088640.0 \\ \log_2(2^{-\hat{p}}) > \log_2\left(671088640.0\right) \\ -\hat{p} > 29.32192 \\ \hat{p} < -29.32192 \\ \end{aligned}

现在我们发现,如果 p^\hat{p} 小于 29-29,则 p[]\mathtt{p}[\,] 中将会出现溢出。然后,

p^=29pˉ=[0.005432,1.2245,0.5,3.2]p[k]=pk2p^=pk229p[]=[2916283,657398432,268435456,1717986918]p[]=[0x002C7FBB,0x272F1AA0,0x10000000,0x66666666]\begin{aligned} \hat{p} &= -29 \\ \bar{p} &= [0.005432,1.2245,-0.5,-3.2] \\ \\ \mathtt{p}[k] &= p_k \cdot 2^{-\hat{p}} \\ &= p_k \cdot 2^{29} \\ \mathtt{p}[\,] &= [2916283, 657398432, -268435456, -1717986918] \\ \mathtt{p}[\,] &= [\mathtt{0x002C7FBB}, \mathtt{0x272F1AA0}, \mathtt{-0x10000000}, \mathtt{-0x66666666}] \\ \end{aligned}

我们可以看到,尝试将这些尾数值左移一位会导致 p[3]\mathtt{p}[3] 溢出到符号位,破坏数值。因此,29-29 确实是最小指数。

第三部分 中,我们将看到一种使用从标量和向量中提取的另一个属性来计算指数的快捷方式,即 头空间(headroom)。