Visual DSP定点数(fract)使用指南
fract数据类型(Fractional Data Type)
C类型 | 用法 |
fract16 | 单16位有符号定点数,1.15 |
fract32 | 单32位有符号定点数1.31 |
fract2x16 | 双16位有符号定点数 |
这几个数据类型实际上并不是固有的数据类型,而是使用typedef语句将short定义成fract16,long定义成fract32和fract2x16。因此,你不能直接通过fract16*fract16来得到正确的定点相乘结果。对于C++,有两个类”fract”和”shortfract”定义了基本的算术操作。以下仅谈在C中的操作。
内建函数(Built-in Functions)
由于fract算术的特殊性,系统提供了一套接口用于实现这些运算,它们定义在fract.h文件中。所有在fract.h中的文件都被标记为“内联”的,所以如果开了编译器优化,内建函数将会被展开。下面给出的内建函数并不全,更多的请参考[1]。
fract16 内建函数
fract16 add_fr1x16(fract16 f1, fract16 f2);
执行16位加法(f1 + f2)
fract16 sub_fr1x16(fract16 f1, fract16 f2);
执行16位减法(f1 – f2)
fract16 mult_fr1x16(fract16 f1, fract16 f2)
执行16位乘法(f1 * f2),结果截短到16位
fract16 multr_fr1x16(fract16 f1, fract16 f2)
执行16位乘法,结果舍入到16位。是否进位根据ASTAT寄存器的RND_MOD是否被设置。
fract32 mult_fr1x32(fract16 f1, fract16 f2)
执行16位乘法,返回32位结果。
fract16 abs_fr1x16(fract16 f1)
返回输入参数的绝对值,如0x8000返回0x7fff。
fract16 min_fr1x16(fract16 f1, fract16 f2)
返回f1,f2的较小值
fract16 max_fr1x16(fract16 f1, fract16 f2)
返回f1,f2的较大值
fract16 negate_fr1x16(fract16 f1)
返回-f1
fract16 shl_fr1x16(fract16 src, short shft)
对src作算术移位,shft为正则向左移,保留符号位,空的位用0填充;shft为负则向右移,符号扩展;
fract32 内建函数
fract32 add_fr1x32(fract32 f1,fract32 f2)
返回f1 + f2
fract32 sub_fr1x32(fract32 f1,fract32 f2)
返回f1 – f2
fract32 mult_fr1x32x32(fract32 f1,fract32 f2)
返回f1*f2,运算的中间结果保存在40位的累加寄存器,然后舍入到32位。
fract32 multr_fr1x32x32(fract32 f1,fract32 f2)
和上一个函数一样,只不过提供了额外的舍入精度
fract32 mult_fr1x32x32NS(fract32 f1, fract32 f2)
提供不饱和的乘法f1 * f2,这个相对来说会快一些
fract32 abs_fr1x32(fract32 f1)
返回f1的绝对值
fract32 min_fr1x32(fract32 f1, fract32 f2)
返回f1, f2的较小值
fract32 max_fr1x32(fract32 f1, fract32 f2)
返回f1, f2的较大值
fract32 negate_fr1x32(fract32 f1)
返回-f1
fract16 sat_fr1x32(fract32 f1)
如果f1 > 0x00007fff,则返回0x7fff,如果f1< 0xffff8000,则返回0x8000,其他情况,返回f1的低16位
fract2x16 内建函数
将两个fract16打包成fract2x16表示为{a,b},”a”为高半部,”b”为低半部
fract2x16 compose_fr2x16(fract16 f1, fract16 f2)
将f1,f2打包成fract2x16的类型
输入:2个fract16值
输出:{f1, f2}
fract16 high_of_fr2x16(fract2x16 f)
返回f的高半部
输入:{a, b}
输出:a
fract16 low_of_fr2x16(fract2x16 f)
返回f的低半部
输入:{a, b}
输出:b
fract2x16 add_fr2x16(fract2x16 f1,fract2x16 f2)
输入:f1{a, b}, f2{c, d}
输出:{a + c, b + d}
fract2x16 sub_fr2x16(fract2x16 f1,fract2x16 f2)
输入:f1{a, b}, f2{c, d}
输出:{a – c, b – d}
fract2x16 mult_fr2x16(fract2x16 f1,fract2x16 f2)
输入:f1{a, b}, f2{c, d}
输出:{trunc16(a*c), trunc16(b*d)}
fract2x16 multr_fr2x16(fract2x16 f1,fract2x16 f2)
输入:f1{a, b}, f2{c, d}
输出:{round16(a*c), round16(b*d)}
fract2x16 negate_fr2x16(fract2x16 f1)
输入:f1{a, b}
输出:{-a, -b}
fract2x16 shl_fr2x16(fract2x16 f1,short shft)
输入:f1{a, b} shft
输出:{a << shft, b << shft}
fract16 sum_fr2x16(fract2x16 f1)
输入:f1{a, b}
输出:a + b
fract2x16 add_as_fr2x16(fract2x16 f1,fract2x16 f2)
输入:f1{a, b}, f2{c, d}
输出:{a + c, b – d}
fract16与fract2x16的一点注解
当编译使用单数据fract16运算的程序时,编译器会尝试优化,寻找可以并行计算的情形,因此重写程序以显式使用fract2x16并不总是能产生性能提高。
fract数据类型的应用(Application of Fractional Data Type)
fract常量的表示
要对一个fract类型(fract16, fract32)赋常量值应该使用r16,r32后缀。
例如:
要为fract16类型的变量a赋值0.5,那么可以使用
a=0x4000或a=0.5r16
两种方式,显然,后者看起来更直观。
分数与浮点数之间的转换
VisualDSP++运行时库提供了高层次的支持用于fract与浮点数之间的转换。这些函数的声明包含在头文件fract2float_conv.h里。
fract32 fr16_to_fr32(fract16); //Deposits a fract16 to make a fract32
fract16 fr32_to_fr16(fract32); //Truncate a fract32 to make a fract16
fract32 float_to_fr32(float); //Convert a float to fract32
fract16 float_to_fr16(float); //Convert a float to fract16
float fr16_to_float(fract16); //Convert a fract16 to float
float fr32_to_float(fract32); //Convert a fract32 to float
综合实例
将fract转换为float并输出
#include <fract.h>
#include <fract2float_conv.h>
int main( void )
{
fract16 x=0.4r16;
float fx;
fx = fr16_to_float(x);
printf("x=%f\n", fx);
return 0;
}
输出:
x=0.399994
fract与float的时间花费对比
代码很简单,这里不再费口舌:
#include <fract.h>
#include <fract2float_conv.h>
#include <time.h>
#define COUNT 10000
int main( void )
{
int i;
fract32 x, y, z;
float fx, fy, fz;
clock_t t1, t2;
//test fract,be sure that the sum won't overflow
t1 = clock();
x = 0; y = 0; z = 0;
for (i = 0; i < COUNT; i++)
{
z = add_fr1x32(z, mult_fr1x32x32NS(x, y));
x = add_fr1x32(x, 0.000001r32);
y = add_fr1x32(y, 0.000001r32);
}
t2 = clock();
printf("result:%f\n", fr32_to_float(z));
printf("fract operations cost:%uk Cycles\n", (t2 - t1) / 1000);
//test float
t1 = clock();
fx = 0; fy = 0; fz = 0;
for (i = 0; i < COUNT; i++)
{
fz += fx * fy;
fx += 0.000001;
fy += 0.000001;
}
t2 = clock();
printf("result:%f\n", fz);
printf("float operations cost:%uk Cycles\n", (t2 - t1) / 1000);
return 0;
}
输出(DEBUG模式)
result:0.333131
fract operations cost:360k Cycles
result:0.333240
float operations cost:5148k Cycles
输出(RELEASE模式)
result:0.333131
fract operations cost:90k Cycles
result:0.333240
float operations cost:5018k Cycles
这里可以看到性能的差异还是很大的。在RELEASE下,性能相差50倍以上。不过这里有一点值得注意,两个运算的结果并不完全一样,相差得有点大。这里没法给出详细的理论分析差异的原因,个人猜想是由于误差累积差成的。因此,在实际应用中,最好注意下不要这种连续累加的情况。使用multr_fr1x32x32结果会更接近一些,不过也差不了多少。
扩展阅读(Further Reading)
参考[1]有关于C++操作fract的详细细节,以及复数fract内建函数的使用,有兴趣的读者请自行了解。这些都在[1]的Compiler->C/C++ Compiler Language->Compiler Built-In Functions章节。还有一些有关映射ETSI(European Telecommunications Standards Institute) fract函数到内建函数的资料,本人也没用过。
参考(References)
[1]“Visual DSP++ 5.0 Compiler and Library Manual for Blackfin Processors”