c语言定点数加减法实验报告,C语言常规优化策略--赋值语句优化

在代码中,若一个变量经赋值后在后面语句的执行过程中不再引用,则这一赋值语句就称为无用赋值。且看下面杜撰的代码段

int DoSmth(int x)

{

int y, z;

if (x>=0)

x=x;

else

x=-x;

y=3;

z=f(x);

return z;

}

其中y=3为无用赋值语句,可以删除,而x=x语句是为了填补条件语句中条件成功分支的空缺,同样是无用的赋值。在这种情况下,可以直接删除该语句,只保留一个分号作为空语句标识,如果为醒目起见,则可用null来代替。下面给出修改后的代码段

int DoSmth(int x)

{

int z;

if (x>=0)

null;

else

x=-x;

z=f(x);

return z;

}

当然程序可以采用更佳的结构以驱除null语句:

int DoSmth(int x)

{

if (x<0)

x=-x;

return f(x);

}

但有时代码中为了保持逻辑上的完整性,或者出于理解代码的原因,有时会出现空语句,建议采用null的写法以警醒自己或其它人。

在C程序中,无效的变量声明应当从程序中删除,当出现无效的变量声明时,编译器一般会用“没有引用的变量”来警告你。

~~~~~~~~~~~~~~

1.2 合并已知量

我们要计算两点之间的距离,相应的点结构及代码的如下:

typedef struct tagPoint

{

double x,y;

} Point;

double Dist(Point P1, Point P2)

{

return sqrt((P1.x-P2.x)* (P1.x-P2.x)+(P1.y-P2.y) (P1.y-P2.y));

}

代码中, P1.x-P2.x,P1.y-P2.y均计算两次,如果我们将一次计算的结果保留下来,就可以减少相应的操作次数

double Dist(Point P1, Point P2)

{

double xDelta= P1.x-P2.x;

double yDelta= P1.y-P2.y;

return sqrt(xDelta*xDelta+yDelta*yDelta);

}

程序设计中还存在一种现象,为了方便,我们通常定义一系列常量,在代码中会反复引用这些常量,例如下面的代码中定义了一个圆周率常量,并在圆周长的计算中出现对它的引用

#define PI 3.1416

double Circum(double r)

{

return 2.0*PI*r;

}

我们可以将常量PI与2.0的计算事先进行合并,以提高Circum函数运算效率

#define PI 3.1416

#define TwoPI   6.2832

double Circum(double r)

{

return TwoPI*r;

}

~~~~~~~~~~~~~

1.3 避免乘法

在C程序中,由于加减运算与位运算一般比乘法快2到10倍,大部分程序员在乘法中出现2的整数次幂(2、4、8、16等)时,往往愿意将乘法操作改造成位操作以提高效率。以z=8x+y为例,多数C程序员会将其写成如下的代码

z=(x<<3)+y;

其中将x右移3位其效果等同于乘8,例如:x=19表示成二进制形式为:

0000000000010011

右移3位变成

0000000010011000

其值为152。

有时,当乘数不是2的整数幂时,出于需要,我们可以根据乘数的二进制表示,将乘法改变成二进制乘法,进一步用移位和加法操作来代替乘法,例如我们要计算z=5x,由于5=4+1,其中1和4均为2的整数次幂,从而z=5x可以表示成z=4x+x,相应的语句为

z=(x<<2)+x;

这一转换通常称为二进制乘法。

二进制乘法在计算机图形图象处理中经常采用,例如,对于640X480的显示屏,一般在计算机内有一块相应的显示缓冲区来保存相应的屏幕元素,我们可以用一个480行,640列的二维数组VideoBuf来指示该缓冲区。屏幕的显示是通过向缓冲区填写数据(颜色或其索引值)而实现的,假设我们要向x列、y行设置一个值color,相应的程序为:

void PlotPixel(int x, int y, int color)

{

*(VideoBuf+(long)640*y+x)=color;

}

VideoBuf为一全局变量,不作为函数的参量来传递。

根据二进制乘法,由640=512+128,可将PlotPixel函数改进为

void PlotPixel(int x, int y, int color)

{

// 640y=512y+128y=((y<<2)+y)<<7

*(VideoBuf+((((long)(y<<2)+y))<<7)+x)=color;

}

有两点值得指出:

(1)二进制乘法会改变程序的可读性,因此,有必要在程序中用注释段说明你的思想。在改进后的PlotPixel函数中用相应的注释指出了此处二进制乘法的原理。

(2)移位运算比”十”、”一”运简优先级要低,因此,在计算z=8x+y时,切不可写成z=x<<3+y。这是程序员常犯的一类错误。

~~~~~~~~~~~~~~~~~~

1.4 避免除法

同样,当以2的整数幂作为除数时,可用移位操作来避免除法,例如

z=x/8+y;

就可以改善为

z=(x>>3)+y;

其中x、y、z均为整数。

对于除数不是2的整数幂的情况,没有一种适当的方法将除法改进为二进制除法。通常的做法有两种:

(1) 将x/y转换为x*(1.0/y),一般来说求倒数比除法快;

(2) 对除数进行规范化,将其变成2的整数幂,然后进行后续处理。例如给定两个整型数组u、v,其维数均为n,我们要将u、v对应元素进行调配以生成一个新的数组w,设r为调配比例,调配公式为

w[i]=ru[i]+(1-r)v[i]

其中r在0、1之间。

在程序实现时,调配比例一般为百分比数值,即用户输入一个百分比Ratio,相应地r=Ratio/100。下面是两个数组进行调配的程序:

void (int *w, int *u, int *v, int n, int Ratio)

{

int i;

for (i=0; i

w[i] = (Ratio*u[i]+(100-Ratio)*v[i])/100;

}

为了提高效率,我们可将比值Ratio规范化为0~128这一范围,记R=Ratio*128/100,相应的调配公式为

w[i] = (R*u[i]+(128-R)*v[i])/128

改进后的程序为:

void (int *w, int *u, int *v, int n, int Ratio)

{

int i;

for (i=0; i

w[i] = ((R*(u[i]-v[i]))>>7)+v[i];

}

为什么不对传入的Ratio参数直接进行限制,将其规范为0~128呢?这是因为Ratio由用户输入,在用户界面的设计时,参数的意义应适合用户的习惯,在本问题中,让用户输入一个百分比值当然比输入一个0~128之间的数要直观得多。

~~~~~~~~~~~~~

1.5 避免浮点运算

C语言中的浮点型float及双精度浮点型double运算比短整型,整型、长整型运算要慢得多,因此避免浮点运算就非常有必要。在上面避免除法运算的函数调配例子中,已经使用到了避免浮点运算的策略,百分比在通常情况下只能用一个浮点数表示,而我们将其表示为整数Ratio与100之比。

1.5.1 中点线算法

避免浮点运算的一个经典例子为Bresenhem的画线算法,直线用两点(x1,y1), (x2,y2)刻划,且要求x1

void PlotLine(int x1, int y1, int x2, int y2, int color)

{

float  m,y,b;  // m为斜率,b为截距

int x, dx, dy;

dx=x2-x1;

dy=y2-y1;

m=(float)dy/(float)dx;

b=(float)(x2*y1-x1*y2)/(float)dx;

for (x=x1; x<=x2; x++)

{

y=m*x+b;

PlotPixel(x, (int)(y+0.5), color);

}

}

朴素算法存在两个缺点:

(1) 涉及浮点操作,画线速度有限;

(2) (int)(y+0.5)为取与y最接近的整数,这将导致精度和时间的损失。

Brensenham提出了一种避开浮点运算的画线算法,但Brensenham的思想讨论起来比较麻烦,这里我们采用Pitteway和Van Aken等采用的中点技术。中点技术从理论上来讲与Brensenham的技术是一致的,特别是在实际画线过程中,两者产生相同的结果。

同朴素画线算法一样,我们限制0

| x  y  1 |

| x1 y1 1 | =0

| x2 y2 1 |

或者

(y2-y1)x-(x2-x1)y+(x2y1-x1y2)=0

F(x,y)=(y2-y1)x-(x2-x1)y+(x2y1-x1y2)

直线F(x,y)=0将平面划分成三个部分

P2

F(x,y)<0   *

*

F(x,y)=0 F(x,y)>0

*

P1  *

中点线算法的基本思想为:

(1) 先画P1点;

(2) 判断P1点右边或右上方的两个点E及NE中哪一个离直线更近,判断方法是确定NE和E的中点M在直线P1P2所确定的三个区域的哪一个内,这时有三种情况:

(i) F(M)=0, E、NE与直线距离相等,因此可任选一个作为直线上一点,通常我们取右边的点E;

(ii) F(M)<0,说明M在直线的上方,E离直线更近,选E作为下一点;

* P2

*NE

|

|M

|

|

P1*-----*E

(iii) F(M)>0,说明M在直线下方,NE离直线更近,选 NE作为下一顶点。

其中F(M)=(x1+1)dy-(y1+0.5)dx+(x1y2-x2y1), 为避开0.5,可用2F(M)作为判别条件。

(3) 更一般,在第p步我们得到直线上一点P(x[p], y[p])后,下一步(x[p+1], y[p+1])怎么选取呢?候选的点只能是P的右点E或右上方的点NE,原因在于直线通过x=x[p]时,交点(x[p], y’[p]满足

y[p]-1/2

而x[p+1]=x[p]+1, 直线与x=x[p]+1的交点确定了y[p+1]的范围为

y[p]-1/2

因为

y’[p+1]=mx[p+1]+b=(mx[p]+b)+m=y’[p]+m

即y[p+1]只能取y[p]或y[p]+1.

至于具体取E或NE,可由(2)中介绍的中点技术确定,由此得到中点线算法:

void MidpointLine (int x1, int y1, int x2, int y2, int color)

{

int dx=x2-x1;

int dy=y2-y1;

int x, y, F;

x=x1;

y=y1;

PlotPixel(x,y,color);

while (x

{

F=2*(x+1)*dy-(2*y+1)*dx+2*(x1*y2-x2*y1);

x++;

if (F<=0)

y++;

PlotPixel(x,y,color);

}

}

需要指出的是:朴素画线算法及中点线算法均可以进一步改进,相应的技术可参考循环优化部分。

1.5.2 定点数算术

若我们在计算中需要用到实数运算,但又不太关心实数的精度时,可以采用一种称之为定点数的算术。预先声明:C语言中没有定点数,它是我们人为造出来的一类数。一般地,我们用一个长整数来表示一个定点数,其前24位表示整数部分,后8位表示小数部分,根据问题的需要可设计相应的定点数。这样,一个长整数类型的数就被重命名为定点数:

typedef  long  fixed;

下面讨论定点数的赋值及算术运算,我们可以将一个整型数、浮点数或一个双精度型数赋给一个定点型变量,相应的形式为

fixed  AssignInt  (int x)

{

return (fixed)x<<8;

}

fixed  AssignLong (long x)

{

return (fixed)(x<<8);

}

fixed AssignFloat (float x)

{

return (fixed)(x*256.0F);

}

fixed Assign Double (double  x)

{

return  (fixed)(x*256.0);

}

由于一个定点数的前24位表示其整数部分,因此对C标准类型数必须乘上256后才能变成定点数。

定点数的加减法与普通加减法类似:

fixed  Add (fixed x, fixed y)

{

return x+y;

fixed Sub(fixed x,fixed y)

{

return x-y;

}

与标准类型稍有差异的是定点数乘法:

fixed Mul (fixed x, fixed y)

{

return ((x*y)>>8);

}

右移8位(除256)的理由在于:设任一定点数x的整数部分为Ix,小数部分为Fx,则

x=256 (Ix+Fx/256)

两个定点数乘法是为了模拟它们所对应的实数乘法,设两个实数为x’,y’,它们对应的定点数为x,y

x’y’=(Ix+Fx/256)(Iy+Fy/256)

=IxIy+(IxFy+FxIy)/256+FxFy/(256*256)

而x’y’的定点数表示为

256IxIy+IxFy+FxIy+FxFy/256

可以看到xy与x’y’的定点数表示之间相差256倍,这就是移8位原因所在。

定点数及其定点数表示相互转化方式为

fixed trans2fixed (int u, int v) //u,v为定点数x的整数部分和小数部分

{

return ((fixed)u<<8)+v;

}

void transfixed2Real(int *u, int *v, fixed x)

{

*u= (int)(x>>8);

*v=(int)x&255;

}

相应地可以设计打印定点数的程序。

上面对定点数算术的讨论均以函数形式进行,在实际使用中,为提高效率, 可利用定点算术的思想直接进行相应的运算,例如我们要完成两个定点数17.28及9.64的一系列运算,相应的代码为

void dosmth()

{

int Ix=17, Fx=28;

int Iy=9, Fy=64;

fixed x, y, z1, z2;

x=((fixed)Ix<<8)+Fx;

y=((fixed)Iy<<8)+Fy;

z1=x+y;    // z1为17.28+ 9.64的定点表示

z2=(x*y)>>8;   // z2为17.8*9.64的定点表示

z=z1-z2;    // z为17.28+9.64-17.28*9.64的定点表示

printf(“x=%d.%d”, (int)(x>>8), (int)x&255);

printf(“y=%d.%d”, (int)(y>>8), (int)y&255);

printf(“x+y-xy=%d.%d”, (int)(z>>8), (int)z&255);

}

关于定点数算术还有两问题需要讨论:

(1) 定点数的精度较低,只能精确表示十进制2位小数。

(2) 为了保证定点数运算不发生溢出,必须要求每一个参予运算的数不能太大,这就引出了定点数最大能表示多大的数的问题。理论上来说,一个定点数最大能表示(2^31-1)/256,但由于加减及乘法运算可能导致溢出,因此最大的数应控制在一定范围内,例如,若x为一定点数,则要保证x*x<=(2^31-1)/256,必须x<=28938。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值