CSAPP基本版实验学习日志:关于fsum代码的阅读以及在ubantu上的运行

fsum(浮点数加法)代码内容:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFSIZE 256

int main(int argc, char *argv[]) {
  char prefix[BUFSIZE];
  char next[BUFSIZE];
    int i;
    float sum = 0.0;
    for (i = 1; i < argc; i++) {
	float x = atof(argv[i]);
	sum += x;
	if (i == 1) {
	  sprintf(prefix, "%.4g", x);
	} else {
	  sprintf(next, " + %.4g", x);
	  strcat(prefix, next);
	  printf("%s = %.4g\n", prefix, sum);
	}
    }
    return 0;
}

下面我将分别解释:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include<stdio.h>是编译预处理命令,即在程序编译之前要处理的内容,<>中以以“.h ”作为结尾的文件称为头文件,如stdio.h,stdlib.h,string.h均为头文件。
stdio 即 “standard input & output"(标准输入输出)在开头加上#include <stdio.h>即可实现c语言中的 输入函数printf、输出函数scanf等函数。
同理,在开头加上#include <stdlib.h>即可实现c语言中的exitmallocfree等函数。
同样的,在开头加上#include <string.h>即可实现关于字符数组的函数,例如连接字符串函数strcat,复制字符串函数strcpy,比较字符串(区分大小写)函数strcmp等。
类似头文件还有math.h(包括各种常用的三角函数、双曲线函数、指数和对数函数等)等。

#define BUFSIZE 256

define即宏定义,一般形式为#define 标识符 替换列表,当在开头加上#define BUFSIZE 256后,即使用此宏定义后,代码中出现BUFSIZE的部分将自动替换为256.宏定义不仅方便程序的修改,而且提高程序的运行效率,是高级程序员常用的操作。替换列表可以是数,字符串,标点符号,运算符,标识符,关键字,字符常量,还可以为空的。注意宏定义不要随意加;否则;也成为宏定义的一部分。更多关于宏定义参考关于#define

int main(int argc, char *argv[]) {

程序的主函数,有main()函数的程序才能运行,即函数必须被main()直接或间接调用才能发挥作用,主函数的返回值类型为int(整型)。其中,main函数可以带参数,这个参数可以认为是main函数的形式参数。C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv。可参考关于main函数参数int main(int argc,char* argv[])详解
C语言还规定argc(第一个形参)必须是整型变量,argv(第二个形参)必须是指向字符串的指针数组。
argc是命令行总的参数个数,argv[]是argc个参数,其中第0个参数argv[0]指向程序运行的全路径名,以后的参数为命令行后面跟的用户输入的参数。
通过阅读后续代码,可以发现它利用了这些参数间关系。

	char prefix[BUFSIZE];
	char next[BUFSIZE];

定义了两个字符型数组(char),数组名为prefix和next,数组长度为256(由宏定义得出)。char为字符,字母,数字,汉字均可视为字符。关于字符还有许多拓展知识,可参考Char数据型态
至于数组,数组是由若干类型相同的相关数据项按顺序存储在一起形成的一组同类型有序数据的集合。注意数组下标从0开始,prefix[BUFSIZE]也就是定义了256长度的数组,其第1至第256个元素分别存在prefix[0]到prefix[255]中。数组还有许多扩展知识,可参考:关于数组

	int i;

定义一个整型变量(int),整型变量,在内存中占4个字节,最长能到32位二进制位,其取值为基本整常数,在该定义中,变量名为i。

	float sum = 0.0;

定义一个浮点型变量(float),变量名为sum,并为其赋初值0.0
浮点数以浮点形式表示,即小数点位置可以是浮动的,最长能到32位二进制位。
浮点数除了float,还有精度更高的double,可参考:​float和double的区别

补充:关于c语言基本数据类型,除了float,double,char和int,还有short,long,可参考c语言基本数据类型short、int、long、char、float、double

    for (i = 1; i < argc; i++) {

for循环语句用来实现当型循环,括号中的表达式1的作用是初始化循环控制变量,即为循环控制变量赋初值;表达式2的作用是给出循环重复执行的判断条件,这个条件也用于决定什么时候结束循环;表达式3的作用是给循环控制变量增值,即定义循环控制变量在每次循环结束后按什么方式变化。
除了for语句,while和do-while语句也是循环语句。关于for语句:C语言for语句用法详解
在此循环中,让i赋初值1,当i < argc时进行循环,每一次循环后i+1,则i >= argc时结束循环,argc由用户输入参数个数决定,也就是在用户不输入其他参数情况下,循环不进行。

		float x = atof(argv[i]);

定义一个浮点型变量(float),变量名为x,并为其赋初值atof(argv[i])。atof函数时一个字符串处理函数,功能是把字符串转换成浮点数,它包含在头文件stdlib.h里。在此程序中,即将数组argv[1]到argv[argc-1]中元素转换为浮点数,就是把用户输入的参数转换为浮点数。
更多关于atof可参考:C语言atof()函数:将字符串转换为double(双精度浮点数)

		sum += x;

C语言中,可将sum=sum+x;语句简写为sum += x;意思是每次循环sum变为自己上一次循环得的值加上x,x每次都不同,由于sum初值为0.0,则这条语句在程序的作用为用户输入的参数依此相加。

		if (i == 1) {

if为条件语句。本程序中使用if-else形式,即
if (条件表达式) 可执行语句 1
else 可执行语句 2
其作用是:若表达式的值为真,则执行可执行语句1,否则执行可执行语句2。
更多关于if语句可参考:C语言if语句

			sprintf(prefix, "%.4g", x);

sprintf的作用是将一个格式化的字符串输出到一个目的字符串中,sprintf的第一个参数应该是目的字符串,如果不指定这个参数,执行过程中出现 "该程序产生非法操作,即将被关闭…"的提示。而printf是将一个格式化的字符串输出到屏幕。
在程序中即i=1时,把x代表的字符串输出在prefix数组中,且数组定义得足够大,故能够放下用户输入的大部分参数。其中%.4g表示根据数据的绝对值大小,自动选取f或e格式中输出宽度较小的一种使用,且不输出无意义的0而.4代表输出小数位数为4位,即4位浮点数输出。
注意:%.4g是以4位小数输出,而%4g意思是输出宽度占4个位置,右对齐。
更多关于sprintf:C语言sprintf()函数

		 } else {
  			sprintf(next, " + %.4g", x);

可执行语句2的内容,这里sprintf的作用是将把x代表的字符串输出在next数组中,并在这之前输出一个+,且依然是4位浮点数输出。

			strcat(prefix, next);

strcat函数功能为连接字符串。 它包含在头文件string.h里。这里将prefix和next的字符串连接在一起并存入prefix中。
更多可参考:C语言strcat()函数

			printf("%s = %.4g\n", prefix, sum);

printf函数是一个标准库函数,它的函数原型在头文件stdio.h中。printf函数调用的一般形式为:printf(“格式控制字符串”, 输出表列)。printf是将一个格式化的字符串输出到屏幕。更多可参考:C语言格式输出函数printf()详解
%s表示输出字符串,在程序中,即输出 prefix的字符串(连接后的字符串) = sum以4位浮点数输出。

		 }
	}
	return 0;
}

主函数返回0表程序结束。

以下为该代码在ubantu上运行结果:

@ubuntu:/mnt/hgfs/share/csapp_code$ ./a.out 1e20 -1e20 3.14
1e+20 + -1e+20 = 0
1e+20 + -1e+20 + 3.14 = 3.14
@ubuntu:/mnt/hgfs/share/csapp_code$ ./a.out -1e20 3.14  
-1e+20 + 3.14 = -1e+20
@ubuntu:/mnt/hgfs/share/csapp_code$ ./a.out -1e20 3.14  1e20
-1e+20 + 3.14 = -1e+20
-1e+20 + 3.14 + 1e+20 = 0

综上,可以看到当输出三个参数1e20(表示1 * 10的20次方) -1e20(表示-1 * 10的20次方) 3.14时,第一次循环即i=1时,执行可执行语句1将x代表的被转换为浮点型的1e20加上sum,并令sum等于新获得的值,此时sum=1e20。并将x以字符串形式给prefix;
第二次循环即i=2时,执行可执行语句2将x代表的被转换为浮点型的-1e20加上sum,并令sum等于新获得的值,此时sum=1e+20 + -1e+20 = 0。并将x以字符串形式前面加上字符“+”和空格给next,然后prefix和next连接并放入prefix的存储空间里,再由printf将prefix和sum的值打出,因此屏幕上显示1e+20 + -1e+20 = 0;
第三次循环即i=3时,执行可执行语句2将x代表的被转换为浮点型的3.14加上sum,并令sum等于新获得的值,此时sum=0 + 3.14 = 3.14。并将x以字符串形式前面加上字符“+”和空格给next,然后prefix和next连接并放入prefix的存储空间里,再由printf将prefix和sum的值打出,因此屏幕上显示1e+20 + -1e+20 + 3.14 = 3.14。
当用户输入两个参数时同理。但通过比较结果可以发现,同样的三个数相加,却因为输入顺序的不同而结果不同。这是由于浮点数相加会对阶的缘故。在第一次输入时,由于先是前两个数相加,而前两个数互为相反数,机器在运算时则直接令其结果为0,最后加上3.14,结果没有任何问题。
而第二次和第三次输入先将-1e20与3.14相加,两数在二进制表示中由于阶码相差过大,出现了“大数吞小数”的问题,-1e20+3.14正确的相加结果由于浮点数的位数不够表示而发生舍入,最终结果只有-1e+20。因此最后-1e+20 + 3.14 + 1e+20 = 0。
总之,这个程序反映了浮点数相加的局限性,即数值相差太大的两数相加会由于位数舍入而结果不正确。这是在程序员为客户编写相关程序时需考虑到的。
博客参考:《深入理解计算机系统》第二章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值