浮点数与定点数¶
Abstract
浮点数的运算开销较大,在 FPGA 上往往需要用定点数来代替浮点数,虽然在精度上会有所损失,但是可以大大提高运算速度。
参考:
原理¶
浮点数转化为定点数
- 决定定点格式: \(Q_{m,n}\) (其中 m 表示整数部分位数,n 表示小数部分位数)
- 将浮点数乘以 2 的 \(n\) 次方(放大操作),以把要转换的浮点数移动到整数部分。
- 最后进行取整操作(一般采用四舍五入方法)。
假设我们要表示的数是 \(x\),其 IEEE 形式为 \(2^{exp}\times 1.frac\),记作 \(x_f\)。要找到其对应的定点数表示 \(x_d\)。
根据定义, \(x_d\) 相当于是最低 \(n\) 位表示小数部分,那么我们让 \(x_d \times 2^n\) 就得到了一个整数,我们记作 \(y\),可以看到 \(y\) 的正常二进制表示和 \(x_d\) 是相同的(即定点表示 \(x_d\) 对应的二进制串等价于 \(x\times 2^n\) 的普通二进制表示)
我们对 \(x_f\) 也乘上 \(2^n\),这样得到了 \(2^{exp+n}\times 1.frac\),此时四舍五入,将 \(frac\) 部分中前 \(n+exp\) 位以后的位全部归 0(因为前 \(n+exp\) 位乘上前面的幂后得到的是整数,后面的部分仍然是小数,这样在四舍五入时就被抹去了)。
四舍五入后,我们将这个数转化为整数类型,此时就从 IEEE 浮点类型转化为了正常的二进制表示,这就是 \(y\),我们可以直接把它作为 \(x\) 的定点数表示。
Examples¶
-
假设我们要将浮点数转为 \(Q_{1, 14}\) 格式的定点数,代码如下:
float limit = 1.99993896484375; int16_t float_to_q14_f(double x) { return ((int16_t)((x) <= limit ? ((x) >= -limit ? (x)*0x7FFF : -0x7FFF) : 0x7FFF)); }
这里我们要先判断
x
是否超过我们能表示的范围。对于 \(Q_{1,14}\) 我们用最高位来表示正负号,次高位表示整数部分,剩下 14 位表示小数。那么所能表示的最大的数是 \((1.11111111111111)_2\),即小数点后 14 个 1,表示的数是 \(1.99993896484375\)。对于超过范围的数,我们直接返回定点数所能表达的最值,否则我们将输入乘上 \(2^{14}\)。 -
假设我们输入的是 32 位浮点数,要将其转为 \(Q_{1,31}\) 格式的定点数,代码如下:
/* x: float 输出 32 位定点数,其中最高位为整数位,剩余 31 位为小数位 */ uint32_t float_to_int32(float x) { if (x >= 1.0) return (1<<31); return (uint32_t)((x) * 0x80000000); }
x=0.5
时函数返回0x4000000
;x=0.67
时函数返回0x55c28f80=0101 0101 1100 0010 1000 1111 1000 0000
,计算可得这个定点数表示的是0.670000016689300537109375
上述的代码常用于对数据预处理时,我们可以将数据先归一化到 \([0,1]\) 范围上,随后就可以转为 32 位定点,只需要 1 位来存储整数位,不会有较大的精度损失。
Hints¶
- 转化过程中可能会有溢出。
- 如何四舍五入。
- 定点数运算时:需要考虑溢出、÷0 和舍入误差;对乘除等运算时需要关注精度。
- 对于有符号数,需要留出一位给符号位。