对于一个数,如果我们无需那么高的位宽(精度),那么就可以对其修约。修约代表着用一个精度更低(但是更易用)的数来替代它。也许世界上最著名的数值修约的例子就是 \pi\approx3.14 了。
从小学开始,老师就会开始教“有效位”、“四舍五入”的规则。对于大部分的数,处理方式都不会有问题。例如 7.3 比起 8,还是更接近于 7。但是 7.5 呢?它在数轴上距离 7 和 8 一样近(0.5),为何我们一般都将其四舍五入到 8 呢?如果是一个负数,例如 -7.5 又应该如何呢?
实际上修约的主要艺术就在于如何花式修约 0.5,常见的方法包括:
Round half up: 7.5 -> 8
, -7.5 -> -7
Round half down: 7.5 -> 8
, -7.5 -> -8
Round half away from 0: 7.5 -> 8
, -7.5 -> -8
Round half towards 0: 7.5 -> 7
, -7.5 -> -7
Round half to even: 7.5 -> 8
, -7.5 -> -8
, 6.5 -> 6
, -6.5 -> -6
Round half to odd: 7.5 -> 7
, -7.5 -> -7
, 6.5 -> 7
, -6.5 -> -7
一般来说,最常用的规则是 3,即四舍五入。这也和 Matlab 中的 round
函数行为一致。另一个在计算机中常用的规则是 5,它是 IEEE 754 中规定的修约方法。
也许上面的修约方法还不够花式,有些地方还能见到更复杂的方式:
实际上,在实数(连续)的世界里,修约规则也许影响并不到大,但在一个定点的 DSP 系统里,或许影响就比较明显了。
FPGA 中的定点数一般采用二进制补码来表示(Two’s complement),我们记为 numerictype(s,w,f)
。三个参数依次为:符号位宽(即有无符号数),总位宽、小数位宽。例如 numerictype(1,8,4)
。整数部分的位宽可以用 w - s - f
算出来。举一些例子:
{+000.0000}_2 = 0
{+000.0001}_2 = 0.0625
{+000.0111}_2 = 0.4375
{+000.1000}_2 = 0.5
{+111.1111}_2 = 7.9375
{-000.0000}_2 = -8
{-000.0001}_2 = -7.9375
{-000.0111}_2 = -7.5625
{-000.1000}_2 = -7.5
{-111.1111}_2 = -0.0625
不是很在意运算结果的细节的时候,最简单的处理方式是直接截位(Truncation)而不进行修约。例如在上例中直接截掉小数部分。对于正数和负数,值都会变小,例如:0.5 -> 0
,-0.5 -> -1
。这和 Matlab 中的 floor
函数的行为是一致的。
在 DSP 系统中,直接截位会为输出信号增加一个直流分量。在一些计算敏感的系统,例如通信系统中,这是会是一个致命错误。忽略“离散”的影响,floor
造成的误差的均值与标准差(假设待修约的数随机均布在每一个可能的值上)分别为:
m_e = -\frac{1}{2}
\sigma_e^2 = \frac{1}{3}
一种非常容易实现的修约方法是,为待修约的数加上 0.5( +000.1000_2 )之后进行直接截位。可以很容易知道,这种修约方法属于类型 1. Round half up。如果同样忽略“离散”的问题,Round half up 造成的误差的均值与标准差分别为:
m_e = 0
\sigma_e^2 = \frac{1}{12}
考虑到离散的问题,m_e 是一个比较小的正数(这正是 up 的意义)。
m_e = \frac{1}{2^{f+1}}
可以看到即便是一种简单的修约,误差的均值(直流分量)和标准差就已经减小了很多。
类似的,我们可以简单的实现 2. Round half down,只需加上 +000.0111_2 后截位即可。
甚至,7. Stochastic rounding 也比较容易实现,只需要随机(伪随机)的加上 +000.1000_2 或 +000.0111_2 即可。
对于 Xilinx 的 FPGA,如果是乘法之后的修约,可以使用 DSP48Ex 中乘法器后的 ALU 来完成,这样可以为 CLB 资源节约一个加法器:
对于其它类型的修约(3、4、5、6),在 FPGA 中进行实现时就需要乘法结果的反馈。例如根据乘法结果的 MSB(符号位)来决定加数,即可实现 3 或 4 类型的修约。但这可能就无法设计成为流水线式结构,造成性能的损失。但好处在于,如果待修约的信号正负数出现的几率相同,那么进行 3 、4 类型的修约后信号是没有直流的。
此外需要注意的是,修约的过程中是有可能溢出的,例如:7.5 -> 8
。Matlab 中的 round
函数会为 fi
类型的数补一个高位。但在 FPGA 中这么做可能就不值得了。
《Rounding Mathods》on mathsisfun
《PG104》of Xilinx Documentation
《PG108》of Xilinx Documentation
《Rounding Algorithms 101》on clivemaxfiel
© 2021 Kele, CC BY-NC-SA 4.0