跳转到内容

浮点/定点数字

来自维基教科书,开放世界中的开放书籍

定点数字是一种简单且易于表达分数的数字,使用固定数量的位数。没有浮点硬件支持的系统经常使用定点数字来表示分数。(“没有浮点硬件支持的系统”包括各种各样的硬件——从高端定点 DSP、FPGA 和昂贵的定制 ASIC,它们处理流媒体的速度比任何浮点单元都快;到极低端的微控制器)。

二进制点

[编辑 | 编辑源代码]

术语“定点”指的是二进制点的位置。二进制点类似于十进制数字的小数点,但由于这是二进制而不是十进制,因此使用不同的术语。在二进制中,位可以是 0 或 1,并且没有单独的符号来指定二进制点的位置。但是,我们想象或假设,二进制点位于数字中指定位之间的一个固定位置。例如,在 32 位数字中,我们可以假设二进制点存在于位 15(15 因为第一个位编号为 0,而不是 1)和 16 之间,为整数部分提供 16 位,为小数部分提供 16 位。请注意,整数字段中的最高有效位通常被指定为符号位,为整数的量级保留 15 位。

宽度和精度

[编辑 | 编辑源代码]

定点数字的宽度是为定点数字分配的总位数。如果我们将整数部分和小数部分存储在不同的存储位置,则宽度将是该数字的总存储量。定点数字的范围是可能出现的最小数字和可能出现的最大数字之间的差。定点数字的精度是该数字小数部分的总位数。因为我们可以定义我们想要将固定二进制点放置在何处,所以精度可以是任何数字,直到且包括数字的宽度。但是请注意,我们拥有的精度越高,我们拥有的总范围就越小。

为了传达用于表示整数和小数部分的位数,使用一种称为Q 格式的符号。例如,Q5.2 表示整数部分是 5 位宽,小数部分是 2 位宽。

并非所有数字都能被定点数字精确表示,因此使用最接近的近似值。

计算 Qm.n 格式中浮点数 (x) 的整数表示 (X) 的公式为

X = round( x*2n )

要转换回来,使用以下公式

x = X * 2-n

Q3.4 格式的一些示例

After conversion:                               After converting them back:
0100.0110 = 4 + 3/8                             4 + 3/8
0001.0000 = 1                                   1
0000.1000 =  1/2                                1/2
0000.0101 =  5/16                               0.3125
0000.0100 =  1/4                                1/4
0000.0010 =  1/8                                1/8
0000.0001 =  1/16                               1/16
0000.0000 = 0                                   0
1111.1111 = -1/16                               -1/16
1111.0000 = -1                                  -1
1100.0110 = -4 + 3/8 = -( 3 + 5/8 )             -4 + 3/8

随机选择的浮点数

0000.1011 = 0.673                               0.6875
0110.0100 = 6.234                               6.25

Q7.8 格式[1](非常常见)中的一些示例

0000_0001.0000_0000 = +1
1000_0001.0000_0000 = -127
0000_0000.0100_0000 = 1/4

由于二进制点的位置完全是概念上的,因此添加和减去定点数字的逻辑与添加和减去整数所需的逻辑相同。因此,在 Q3.4 格式中将二分之一加二分之一时,我们期望看到

 0000.1000
+0000.1000
__________
=0001.0000

这等于 1,正如我们所期望的那样。这同样适用于减法。换句话说,当我们添加或减去定点数字时,和(或差)中的二进制点将位于与我们操作的两个数字中完全相同的位置。

当我们乘以两个 8 位定点数字时,我们将需要 16 位来保存乘积。显然,由于结果中的位数与输入不同,因此应预期二进制点会移动。但是,它的工作原理与十进制完全相同。

当我们在十进制中乘以两个数字时,小数点的位置在乘积的右端数字的左侧 N 位,其中 N 是乘数和被乘数小数点右侧的数字位数的总和。因此,在十进制中,当我们乘以 0.2 乘以 0.02 时,我们得到

  0.2
x0.02
_____
0.004

乘数的小数点右侧有一位,被乘数的小数点右侧有两三位。因此,乘积的小数点右侧有三位(也就是说,小数点位于左侧三位)。

它在二进制中也是一样的。

从上面的加法示例中,我们知道 Q3.4 格式中的二分之一等于十六进制的 0x8。由于十六进制中的 0x8 乘以 0x8 是十六进制中的 0x0040,因此定点结果也可以预期为 0x0040——只要我们知道二进制点的位置。让我们用二进制写出乘积

0000000001000000

由于乘数和被乘数的二进制点右侧都有四位,因此乘积中二进制点的位置在左侧八位。因此,我们的答案是 00000000.01000000,正如我们所期望的那样,等于四分之一。

如果我们希望输出格式与输入格式相同,则必须限制输入范围以防止溢出。将 Q7.8 转换回 Q3.4 只需将乘积向右移 4 位即可。

FIR 滤波器

[编辑 | 编辑源代码]

定点数字通常在数字滤波器(包括 FIR 和 IIR 滤波器)内部使用。

使用定点数字实现 FIR 和 IIR 算法有一些实际考虑因素。[2][3]

正弦表

[编辑 | 编辑源代码]

许多生成正弦波的嵌入式系统(如 DTMF 生成器)在程序内存中存储一个“正弦表”。(它用于逼近数学正弦() 和余弦() 函数)。由于此类系统通常的程序内存非常有限,因此当使用此类表时,通常以两种不同的方式使用定点数字:存储在表中的值以及用于索引这些表的“布拉德”。

存储在正弦表中的值

[编辑 | 编辑源代码]

通常,正弦和余弦函数的一个象限被存储在该表中。通常它是一个象限,在这个象限中,这些函数在 0 到 +1 的范围内产生输出值。这种表中的值通常存储为定点数字——通常是 16 位无符号 Q0.16 格式的数字或 8 位无符号 Q0.8 值的数字。似乎有两种流行的方法来处理 Q0.16 不能完全处理 1.0 的事实,它只能处理 0 到 (1.0-2^-16) 之间的数字: (a) 按正好是二的幂(在本例中为 2^16)进行缩放,就像大多数其他定点系统一样,并将过大的值替换(裁剪)为可以存储的最大值:因此 0 表示为 0,0.5 表示为 0x8000,(1.0-2^-16) 表示为 0xFFFF,1.0 被截断并也表示为 0xFFFF。[4] (b) 按可能的最大值(在本例中为 0xFFFF)进行缩放,因此最大值和最小值都可以完全表示:因此 0 表示为 0,(1.0-2^-16) 表示为 0xFFFE,1.0 恰好表示为 0xFFFF。[5]

一些人用贝塞尔样条曲线绘制相当精确的圆并计算相当精确的正弦和余弦。该“表”成为 8 个值,代表一个单一的贝塞尔曲线,它以大约 4 ppm 的精度近似一个圆的 1/8,或以大约千分之一的精度近似一个圆的 1/4。[6][7]

一些使用极度受内存限制和速度缓慢的 8 位处理器的人使用一系列用定点算术计算的抛物线来计算正弦和余弦的平滑近似值。[8] 该“表”成为一个非常简单的公式,代表一个单一的抛物线。对于 0 到 90 度(以度为单位)的角度 A,

  • sin(A) ~= 1-((A-90)/90)^2,当 0 <= A <= 180 时,最大误差小于 0.06
  • cos(A) ~= 1-(A/90)^2,当 -90 <= A <= 90 时,最大误差小于 0.06

转向

[edit | edit source]

许多人更喜欢用“圈数”来表示旋转(例如角度)。“圈数”的整数部分表示发生了多少次完整旋转。当用标准有符号定点算术乘以 360(或 1τ = 2π[9])时,“圈数”的小数部分会得到 -180 度(-π 弧度)到 +180 度(+π 弧度)范围内的有效角度。在某些情况下,在二进制角度上使用无符号乘法(而不是有符号乘法)很方便,这会得到 0 到 +360 度(+2π 弧度)范围内的正确角度。

将角度存储为圈数的定点分数的主要优点是速度。将某个“当前位置”角度与某个正或负的“增量角度”组合以获得“新位置”速度非常快,即使在缓慢的 8 位微控制器上也是如此:它只需要进行一次“整数加法”,忽略溢出。其他存储角度的格式需要相同的加法,加上处理 360 度溢出或 0 度下溢边缘情况的特殊情况。

与将角度存储在二进制角度格式相比,将角度存储在任何其他格式中——例如 360 度表示一个完整的旋转,或 2π 弧度表示一个完整的旋转——不可避免地会导致一些位模式产生超出该范围的“角度”,需要额外的步骤将值范围缩减到所需的范围,或者会导致一些位模式根本不是有效角度(NaN),或者两者都有。

在“圈数”单位中使用二进制角度格式允许我们快速地(使用移位和掩码,避免乘法)将位分离成

  • 表示整数圈数的位(在查找角度的正弦时被忽略;有些系统根本不费心存储这些位)
  • 表示象限的 2 位
  • 直接用于索引查找表的位
  • 小于索引表中的一个“步长”的低位(相位累加器位,在没有插值的情况下查找角度的正弦时被忽略)

[10][11][12]

低位相位位即使没有插值也能提高频率分辨率。

有些系统使用低位在表中的值之间进行线性插值。[13] 这样一来,你就可以用更小的表获得更高的精度(节省程序空间),代价是多花几个周期来进行这个“额外”的插值计算。一些系统通过牺牲几个周期来使用这些低位进行三次插值计算来获得更高的精度,从而获得更小的表。[4]

也许最常见的二进制角度格式是“brad”。

许多嵌入式系统将角度(“圈数”的小数部分)存储在单字节二进制角度格式中。[14] 有几种解释该字节中值的方法,这些方法或多或少地意味着相同的角度

  • 以 brad(二进制弧度)为单位的角度,存储为 8 位无符号整数,从 0 到 255 brad
  • 以 brad 为单位的角度,存储为 8 位有符号整数,从 -128 到 +127 brad
  • 以“圈数”为单位的角度,存储为无符号 Q0.8 格式的圈数分数,从 0 到略小于 1 个完整的圈数
  • 以“圈数”为单位的角度,存储为有符号 Q0.7 (?) 格式的圈数分数,从 -1/2 到略小于 +1/2 个完整的圈数

一个完整的圈数[15] 是 256 brad[16] 是 360 度。

如果一个字节的精度不够,brad 系统可以很容易地扩展到更多的小数位——每圈 65,536 个计数可以用 16 位表示。[17]

更多阅读材料

[edit | edit source]

参考资料

[edit | edit source]
  1. Bruce R. Land. "GCC 和汇编中的定点数学函数".
  2. Randy Yates. "定点 FIR 滤波器实现中的实际考虑". 2010.
  3. Randy Yates. "定点算术:简介". 2013.
  4. a b Brad Eckert:用 16 个等间距的 x 每 1/4 周期 三次样条曲线 近似正弦“提供比 16 位精度更好的精度”sin(x)。[1]
  5. "重新规范化正弦表"
  6. Don Lancaster. "使用四个贝塞尔三次样条曲线近似圆或椭圆。" "教程推导了从两个到八个不同样条拟合的近似数学方法。"
  7. 维基百科:贝塞尔样条曲线#近似圆弧
  8. Tony P. "正弦脉冲 LED PWM 技巧:8 位方法".
  9. Michael Hartl. 陶数宣言:π 是错误的。 2010.
  10. 声音合成理论/振荡器和波形表
  11. 使用波形表生成正弦波的描述
  12. Eric Smith. "在 PIC 上实现正弦函数"。 [2](没有插值 - 速度快但粗糙)
  13. Scott Dattalo. "使用 PIC 生成正弦波"。 [3](使用插值 - 精度更高但速度更慢)
  14. Futurebasic/语言/参考/圆
  15. 维基百科:圈数(几何)
  16. 维基百科:二进制弧度
  17. Garth Wilson. "用于超快、精确、16 位定点/缩放整数数学的大型查找表:三角学中的角度"。 [4]. 2012.
华夏公益教科书