避开浮点数——伪浮点数
来源:互联网 发布:如何下载visio软件 编辑:程序博客网 时间:2024/05/21 06:17
对于定点处理器来说,浮点数学运算的代价是非常巨大的(大多数嵌入式应用都是定点处理器)。相对于整数数学运算,浮点数学运算慢的要死。每个操作都会调用某个库来实现相应的函数,而这个库通常相当大。即使所使用的浮点数学非常简单,应用程序的大小也是非常惊人的。
(标准的输入输出函数(例如printf和iostream)通常包含浮点处理。即使代码中没有使用浮点,但如果使用了这些函数,那么代码还是会包含浮点算术库。)
除非有很好的理由,否则就应该像躲避瘟疫一样避开浮点数。如果不能避开,可以用伪装。
先介绍二级制换算,这是后面的基础。
除法运算是个成本相对较高的步骤,但是如果是除以常数,则有些方法可以解决这个问题,我们可以将除法用位运算来近似。
以Input/6为例。
Input/6如果将分子和分母都放大同样大的倍数,然后将分母换成离它最近的能用2的幂表示的数,则分母可以写成2的多少次方,然后就可以换成移位运算了。
表1:用2的幂为除数近似1/6
乘数
除数
等价移位
结果
误差%
1
6
无
0.166 666 667
0
3
16
4
0.1875
12.5
5
23
5
0.156 25
6.2
11
64
6
0.171 875
3.1
21
128
7
0.164 063
1.5
43
256
8
0.167 969
0.78
85
512
9
0.166 016
0.39
171
1024
10
0.166 992
0.19
341
2048
11
0.166 504
0.09
683
4096
12
0.166 748
0.04
注意,每多一次移位,这个表中误差就减半。
#define DIVIDE_THREE_FACT_MULT 171
#define DIVIDE_THREE_FACT_SHIFT 10
int16_t DivideThreeFactorial(int16_t input){
int32_t tmp = input*DIVIDE_THREE_FACT_MULT;
return (tmp >> DIVIDE_THREE_FACT_SHIFT);
}
注意,输入和输出都是16位,但是为了存放乘法的结果必须要用32位的变量。即使使用了较大的临时变量,这个函数也比除法成本低。
通过这种近似会降低精度。
12.345可以表示为49/4,误差为0.095。使用更大的分母移位值,我们能得到更大的精度。但是,分母太大会导致分子溢出
表2:用二进制换算表示数值12.345
分子
分子需要的位数
分母移位值
等效浮点数
误差
12
4
0
12
0.345
25
5
1
12.5
0.155
99
7
3
12.375
0.030
395
9
5
12.343 75
0.001 25
126 41
14
10
12.344 726 56
0.000 273
12 944 671
24
20
12.345 000 27
2.67E-07
414 229 463
29
25
12.345
1.19E-09
1 656 917 852
31
27
12.345
1.19E-09
因此,在分母中做移位可以减小误差,但同时也增加了分子需要的位数。这又将我们带回到两个问题:对这个算数运算,系统可以忍受多大的误差?经过算法中的乘法和加法运算之后期望的结果值有多大?
伪浮点数
回忆小时候,我们还不知道什么叫浮点数,但是我们知道0.25,可以写成1/4。任何有理数都可以写成分数。即使无理数也可以近似用分数表示,而且分母大点儿误差就会更小。例如,π通常近似成22/7。
我们现在想要避开浮点数,所以想到用分数表示,但是除法的代价也不低,所以可以用上面提到的二进制换算。
定义伪浮点数
struct sFakeFloat{
int32_t num; //numerator
int8_t shift; //right-shift value (use negative for left-shift)
}
这个结构中存放的数表示:
floatingPointValue = num/2^shift (数学意义)
floatingPointValue = num >> shift; //in the actual code
负的移位值示意移位方向的改变
struct sFakeFloat four = { 1, -2 };
floatingPointValue = four.num >> shift ==> 1/(2^-2)==>1*2^2==>4
加法(和减法)
struct sFakeFloat{
Int8_t num;
int8_t shift;
}
举例:
struct sFakeFloat a = { 99, 3 }; //12.375, not quite 12.345
struct sFakeFloat a = { 111, 5 }; //3.46875, not quite 3.456
struct sFakeFloat result; //15.84375, close to ideal of 15.831
int16_t tmp = a.num;
tmp = tmp << ( b.shift - a.shift );
这里必须使用更大的临时变量,否则结果可能溢出并被截断。注意,分子并不是唯一会发生溢出的部分。分母必须小于8位,为了避免溢出,除了对较小的分母做移位放大外,还需要对较小的分母做移位缩小(除以2,精度减小)。
分子的和存放在临时变量中,直到我们确信它足够小,能够放进结果中。
tmp = tmp + b.num;
此处,移位值取b.shift=5,临时变量是507,太大了,不能用8位表示。需要将分子变小。
result.shift = b.shift;
while( tmp > INT8_MAX || tmp < -INT8_MAX){
tmp = tmp >> 1;
Result.shift--;
}
乘法(和除法)
乘法比加法要简单一些,因为不需要做分母分配。对于乘法,只需将分子和分母分别相乘就得到结果(2/3 x 5/3 = 10/9).
首先将一个数的分子放进临时变量中,以防止发生溢出的错误。加法需要检查结果的大小,而乘法不用,可以坑定两个32位变量的乘法不会超过64位。然后用第二个数的分子乘以这个临时变量。(注意64位整数可能并非是处理器内置的数据类型,因此使用会产生一些额外开销,但比使用浮点型(float或则double)变量产生的额外的开销少多了)
和加法一样,只要分子相乘的结果不能放进结果的分子中,就需要改变移位值来避免溢出。
确定误差
要想结果最优,对于将要处理的数字需要预先了解,这样才能够处理溢出,保证系统的稳定性。可以创建一个非常通用的库来处理每一个可能的情况,但这样做的风险是需要重新实现浮点数。在评判算法的时候,需要知道变量的范围以及在每个时间处理变量所需要的精度。
以满足系统要求为目的,进行误差检查,然后改变一些参数,继续迭代检查,使之达到要求。
1.决定分子需要有多少位。
2.选择一个移位值,使分子能放进可用的位数中。注意是否有一个可接受的移位值范围。
3.确认数据的极端值可以用二进制换算的格式表示而不会发生溢出。
4.确认粒度是可以接受的,即应该小于目标误差。
5.检查极端值和额定值的误差。这些误差应该总是比粒度小,但能够提供对换算值是否有效的另一种检查机制。
- 避开浮点数——伪浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- 浮点数
- C++ primer 第三章总结 100-155
- 创业三个月总结---记录这三个月的酸甜苦辣咸!!!
- wow私服,arcemu trunk源码编译架设
- SDUT 猜糖块
- android:scaleType属性
- 避开浮点数——伪浮点数
- [Android开发]android 设备唯一码的获取,Cpu号,Mac地址
- 运动目标检测与跟踪笔记
- C++ Primer总结与内容拓展(第五章)
- 只看以太网本地连接不显示隧道适配器本地连接的dos命令
- 创建自定义鼠标形状
- mysql数据库大数据量优化方式
- 笔记本+win7下USB转串口驱动的安装
- android初学者——自定义控件(继承View)