介绍
本APP注释介绍如何使用ARM C编译器和ARM或精简程序集创建高效的指向算术代码。 因为ARM核心是整数处理器,所以必须用整数运算模拟所有浮点运算。 使用定点算法而不是浮点数可以大大提高许多算法的性能。
本文档包含以下部分:
定点运算原理:描述了定点运算所需的数学概念。
例如:给出了为了信号处理和图形处理而制作定点代码的例子。 这是两种最常见的用途。
C编程:涵盖了在c中实现定点代码的实际细节。 示例程序与一组宏定义一起提供。
汇编程序:介绍汇编程序的示例。
定点算法原理
在计算数学中,可以使用一对整数(n,e ) (尾数和指数)来近似表示分数。 这是整数,表示分数n2en2e。 指数e可以认为在放置二进制小数点之前必须移动n的位数。 例如
我是Mantissa(n )
是exponent(e )
伯尼
最差
01100100
-1
011001000.
200
01100100
0
01100100.
100
01100100
1
0110010.0
50
01100100
2
011001.00
25
01100100
3
01100.100
12.5
01100100
7
0.1100100
0.78125
如果e是变量,保存在寄存器中,并且在编译时未知,则(n,e )称为浮点数。 如果事先知道e的话,在编译时,(n,e )被称为固定点数。 通过存储尾数,可以将固定点数存储在标准整数变量(或寄存器)中。
关于常数点,指数e通常用字母q表示。 以下各节介绍如何对两个常量执行基本算术运算。 两个算术是a=n2pa=n2p和b=m2qb=m2q,结果表示为c=k2rc=k2r。 其中p、q和r是固定的常数指数。
计算时,只需通过尾数n m的两个计算得到k。 在其中使用他们的指数进行转换。 实际存储数字时也只存储尾数。 指数由另一个对应的数字保存。
指数变化
对定点数执行的最简单的操作是改变指数。 为了将指数从p变为r (执行操作c=a ),可以通过简单的移位根据n计算随机数k。
因为n2p=n2rp2r n2p=n2rp2r
k=n=p(if ) r=p ) ) ) ) )。
k=n(pr ) k=n ) pr ) if ) pr ) if(pr ) )
加法和减法
要进行c=a b的操作,首先将a和b变换为与c相同的指数r,然后加上尾数。 这个方法如下。
N2RM2R=(NM ) N2RM2R=(NM ) 2R
减法相似
乘法
乘积c=ab可以通过简单的整数乘法来执行。 等式如下。
ab=N2PM2q=(nm )2) ) PQ ) ab=N2PM2q=(nm ) 2(pq ) ) ) ) ) 65
因此,积n * m是指数p q的答案的尾数。 要将答案转换为指数r,请按上述方式进行移位。 例如
k=(nm ) ) pQR ) k=(nm ) ) pQR ) if ) pq=r ) if ) pq=r )
除法
c=a/b也可以通过简单的整数除法来执行。 公式如下。
a/b=(N2P )/M2Q ) ) n/m ) 2qp=) n/m ) 2r qp2ra/b=(n2p )/M2Q ) ) n/m ) 2rqp2r )
为了不丢失精度,必须在除以m之前执行2r qp2r qp乘法。 例如,
k=(n=p ) if ) rq=p ) )。
平方根
平方根的公式如下。
a=N2P=N22RP2ra=N2P=N22RP2R
换句话说,isqr是整数的平方根函数(例如,c=ac=a执行isqr(n2RP ) k=isqr ) n2RP )。
溢出
上述等式显示了对定点形式的数字只使用整数运算来执行加、减、乘、除、根提取的基本操作的方法。 但是,为了将答案准确存储在2r2r ()表示的结果中,必须确保在计算过程中不会发生溢出。 如果没有仔细选择指数,则每次左移时,加/减或乘法都会溢出,成为没有意义的答案。
以下“现实世界”示例说明了如何选择指数。
实例
信号处理
在信号处理中,数据通常由-1到1之间的分数组成。
此示例使用指数q和32位整数尾数(以便每个值可以保存在一个ARM寄存器中)。 为了能够将两个数字相乘而不会溢出,您需要2q<32或q<=15。 在实践中,通常选择q = 14,因为这允许与几个积累相乘而没有溢出风险。在编译时固定q = 14。 然后0xFFFFC000表示-1,0x00004000表示+1和0x00000001表示2−142−14,最精确的除数。
假设x,y是两个q = 14尾数,n,m是两个整数。 通过应用上面的公式,您可以得到基本操作,其中 a 为正常的整数:
Operation
Code to produce mantissa of answer in q=14 format
x + y
x + y
x + a
x + (a<<14)
xa
x*a
xy
(x*y)>>14
x/a
x/a
a/b
(a<<14)/b
x/y
(x<<14)/y
x−−√x
sqr(x<<14)
图像处理
在这个例子中,(x,y,z)坐标以八位小数信息(q = 8)以小数形式存储。 如果x存储在一个32位寄存器中,那么x的最低字节给出小数部分,最高的三部分是整数部分。
要计算距离原点的距离d,以q = 8的形式:d=x2+y2+z2−−−−−−−−−−√d=x2+y2+z2
如果您直接应用上述公式并将所有中间答案以q = 8的形式保存,则会得到以下代码:
x = (x*x)>>8 square x
y = (y*y)>>8 square y
z = (z*z)>>8 square z
s = x+y+z sum of squares
d = sqrt(s<<8) the distance in q=8 form
1
2
3
4
5
或者,如果您将中间答案保持为q = 16格式,则减少班次数并提高准确度:
x = x*x square of x in q=16 form
y = y*y square of y in q=16 form
z = z*z square of z in q=16 form
s = x+y+x sum of squares in q=16 form
d = sqr(s) distance d in q=8 form
1
2
3
4
5
总结
如果你以q形式相加两个数字,它们将保持q-form。
如果你用q形式乘以两个数字,答案是2q形式。
如果你采用q形式的数字的平方根,答案是q / 2形式。
要从q-form转换为r-form,你需要向左移(r-q)或向右移(q-r),取决于q和r中哪一个更大
要获得最佳精度结果,请选择最大的q,并保证中间计算不能溢出。
C 程序
可以使用标准整数算术运算在C中对定点算术进行编程,并且当需要时(通常在确保答案仍然是q形式之前或之后的操作之前或之后)使用移位来改变q-形式。
为了使编程更容易阅读,已经定义了一组C宏。 下面的示例程序定义了这些宏并说明了它们的用法:
注:编译需要在末尾加上 -lm 选项 ,如gcc fixed.c -o fixed -lm
/* A Simple C program to illustrate the use of Fixed Point Operations */
#include
#include
#include
/* DEFINE THE MACROS */
/* The basic operations perfomed on two numbers a and b of fixed
point q format returning the answer in q format */
#define FADD(a,b) ((a)+(b))
#define FSUB(a,b) ((a)-(b))
#define FMUL(a,b,q) (((a)*(b))>>(q))
#define FDIV(a,b,q) (((a)<
/* The basic operations where a is of fixed point q format and b is
an integer */
#define FADDI(a,b,q) ((a)+((b)<
#define FSUBI(a,b,q) ((a)-((b)<
#define FMULI(a,b) ((a)*(b))
#define FDIVI(a,b) ((a)/(b))
/* convert a from q1 format to q2 format */
#define FCONV(a, q1, q2) (((q2)>(q1)) ? (a)<>((q1)-(q2)))
/* the general operation between a in q1 format and b in q2 format
returning the result in q3 format */
#define FADDG(a,b,q1,q2,q3) (FCONV(a,q1,q3)+FCONV(b,q2,q3))
#define FSUBG(a,b,q1,q2,q3) (FCONV(a,q1,q3)-FCONV(b,q2,q3))
#define FMULG(a,b,q1,q2,q3) FCONV((a)*(b), (q1)+(q2), q3)
#define FDIVG(a,b,q1,q2,q3) (FCONV(a, q1, (q2)+(q3))/(b))
/* convert to and from floating point */
#define TOFIX(d, q) ((int)( (d)*(double)(1<
#define TOFLT(a, q) ( (double)(a) / (double)(1<
#define TEST(FIX, FLT, STR) {
a = a1 = randint();
b = bi = a2 = randint();
fa = TOFLT(a, q);
fa1 = TOFLT(a1, q1);
fa2 = TOFLT(a2, q2);
fb = TOFLT(b, q);
ans1 = FIX;
ans2 = FLT;
printf("Testing %sn fixed point answer=%fn floating point answer=%fn",
STR, TOFLT(ans1, q), ans2);
}
int randint(void) {
int i;
i=rand();
i = i & 32767;
if (rand() & 1) i=-i;
return i;
}
int main(void) {
int q=14, q1=15, q2=7, q3=14;
double fa, fb, fa1, fa2;
int a,b,bi,a1,a2;
int ans1;
double ans2;
/* test each of the MACRO's with some random data */
TEST(FADD(a,b), fa+fb, "FADD");
TEST(FSUB(a,b), fa-fb, "FSUB");
TEST(FMUL(a,b,q), fa*fb, "FMUL");
TEST(FDIV(a,b,q), fa/fb, "FDIV");
TEST(FADDI(a,bi,q), fa+bi, "FADDI");
TEST(FSUBI(a,bi,q), fa-bi, "FSUBI");
TEST(FMULI(a,bi), fa*bi, "FSUBI");
TEST(FDIVI(a,bi), fa/bi, "FSUBI");
TEST(FADDG(a1,a2,q1,q2,q3), fa1+fa2, "FADDG");
TEST(FSUBG(a1,a2,q1,q2,q3), fa1-fa2, "FSUBG");
TEST(FMULG(a1,a2,q1,q2,q3), fa1*fa2, "FMULG");
TEST(FDIVG(a1,a2,q1,q2,q3), fa1/fa2, "FDIVG");
printf("Finished standard testn");
/* the following code will calculate exp(x) by summing the
series (not efficient but useful as an example) and compare it
with the actual value */
while (1) {
printf("Please enter the number to be exp'ed (<1): ");
scanf("%lf", &fa);
printf(" x = %fn", fa);
printf(" exp(x) = %fn", exp(fa));
q = 14; /* set the fixed point */
a = TOFIX(fa, q); /* get a in fixed point format */
a1 = FCONV(1, 0, q); /* a to power 0 */
a2 = 1; /* 0 factorial */
ans1 =0; /* series sum so far */
for (bi=0 ; bi<10; bi++) { /* do ten terms of the series */
int j;
j = FDIVI(a1, a2); /* a^n/n! */
ans1 = FADD(ans1, j);
a1 = FMUL(a1, a, q); /* increase power of a by 1 */
a2 = a2*(bi+1); /* next factorial */
printf("Stage %d answer = %fn", bi, TOFLT(ans1, q));
}
}
return 0;
}