支持C语言的数学运算和小数

C语言中的加号、减号与数学中的一样,乘号、除号不同;另外C语言还多了一个求余数的运算符,就是 %。

下面的代码演示了如何在C语言中进行加减乘除运算:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 12;
  5. int b = 100;
  6. float c = 8.5;
  7. int m = a + b;
  8. float n = b * c;
  9. double p = a / c;
  10. int q = b % a;
  11. printf("m=%d, n=%f, p=%lf, q=%d\n", m, n, p, q);
  12. return 0;
  13. }

输出结果:
m=112, n=850.000000, p=1.411765, q=4

你也可以让数字直接参与运算:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 12;
  5. int b = 100;
  6. float c = 8.9;
  7. int m = a - b; // 变量参与运算
  8. int n = a + 239; // 有变量也有数字
  9. double p = 12.7 * 34.3; // 数字直接参与运算
  10. printf("m=%d, n=%d, p=%lf\n", m, n, p);
  11. printf("m*2=%d, 6/3=%d, m*n=%ld\n", m*2, 6/3, m*n);
  12. return 0;
  13. }

输出结果:
m=-88, n=251, p=435.610000
m*2=-176, 6/3=2, m*n=-22088

对除法的说明

C语言中的除法运算有点奇怪,不同类型的除数和被除数会导致不同类型的运算结果:

  • 当除数和被除数都是整数时,运算结果也是整数;如果不能整除,那么就直接丢掉小数部分,只保留整数部分,这跟将小数赋值给整数类型是一个道理。
  • 一旦除数和被除数中有一个是小数,那么运算结果也是小数,并且是 double 类型的小数。


请看下面的代码:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 100;
  5. int b = 12;
  6. float c = 12.0;
  7. double p = a / b;
  8. double q = a / c;
  9. printf("p=%lf, q=%lf\n", p, q);
  10. return 0;
  11. }

运行结果:
p=8.000000, q=8.333333

a 和 b 都是整数,a / b 的结果也是整数,所以赋值给 p 变量的也是一个整数,这个整数就是 8。

另外需要注意的一点是除数不能为 0,因为任何一个数字除以 0 都没有意义。

然而,编译器对这个错误一般无能为力,很多情况下,编译器在编译阶段根本无法计算出除数的值,不能进行有效预测,“除数为 0”这个错误只能等到程序运行后才能发现,而程序一旦在运行阶段出现任何错误,只能有一个结果,那就是崩溃,并被操作系统终止运行。

请看下面的代码:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a, b;
  5. scanf("%d %d", &a, &b); //从控制台读取数据并分别赋值给a和b
  6. printf("result=%d\n", a / b);
  7. return 0;
  8. }
这段代码用到了一个新的函数,就是 scanf。scanf 和 printf 的功能相反,printf 用来输出数据,scanf 用来读取数据。此处,scanf 会从控制台读取两个整数,并分别赋值给 a 和 b。关于 scanf 的具体用法,我们将在一节中详细讲解,这里大家只要知道它的作用就可以了,不必求甚解。

程序开头定义了两个 int 类型的变量 a 和 b,程序运行后,从控制台读取用户输入的整数,并分别赋值给 a 和 b,这个时候才能知道 a 和 b 的具体值,才能知道除数 b 是不是 0。像这种情况,b 的值在程序运行期间会改变,跟用户输入的数据有关,编译器根本无法预测,所以就没法及时发现“除数为 0”这个错误。

对取余运算的说明

取余,也就是求余数,使用的运算符是 %。C语言中的取余运算只能针对整数,也就是说,% 的两边都必须是整数,不能出现小数,否则编译器会报错。

另外,余数可以是正数也可以是负数,由 % 左边的整数决定:

  • 如果 % 左边是正数,那么余数也是正数;
  • 如果 % 左边是负数,那么余数也是负数。


请看下面的例子:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. printf(
  5. "100%%12=%d \n100%%-12=%d \n-100%%12=%d \n-100%%-12=%d \n",
  6. 100%12, 100%-12, -100%12, -100%-12
  7. );
  8. return 0;
  9. }

运行结果:
100%12=4
100%-12=4
-100%12=-4
-100%-12=-4 

在 printf 中,% 是格式控制符的开头,是一个特殊的字符,不能直接输出;要想输出 %,必须在它的前面再加一个 %,这个时候 % 就变成了普通的字符,而不是用来表示格式控制符了。

加减乘除运算的简写

有时候我们希望对一个变量进行某种运算,然后再把运算结果赋值给变量本身,请看下面的例子:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 12;
  5. int b = 10;
  6. printf("a=%d\n", a);
  7. a = a + 8;
  8. printf("a=%d\n", a);
  9. a = a * b;
  10. printf("a=%d\n", a);
  11. return 0;
  12. }

输出结果:
a=12
a=20
a=200

a = a + 8相当于用原来 a 的值(也即12)加上 8,再把运算结果(也即20)赋值给 a,此时 a 的值就变成了 20。

a = a * b相当于用原来 a 的值(也即20)乘以 b 的值(也即10),再把运算结果(也即200)赋值给 a,此时 a 的值就变成了 200。

以上的操作,可以理解为对变量本身进行某种运算。

在C语言中,对变量本身进行运算可以有简写形式。假设用 # 来表示某种运算符,那么

a = a # b

可以简写为:

a #= b

# 表示 +、-、*、/、% 中的任何一种运算符。

上例中a = a + 8可以简写为a += 8a = a * b可以简写为a *= b

下面的简写形式也是正确的:

  1. int a = 10, b = 20;
  2. a += 10; //相当于 a = a + 10;
  3. a *= (b-10); //相当于 a = a * (b-10);
  4. a -= (a+20); //相当于 a = a - (a+20);

注意:a #= b 仅是一种简写形式,不会影响程序的执行效率。

 

小数分为整数部分和小数部分,它们由点号.分隔,例如 0.0、75.0、4.023、0.27、-937.198 -0.27 等都是合法的小数,这是最常见的小数形式,我们将它称为十进制形式。

此外,小数也可以采用指数形式,例如 7.25×102、0.0368×105、100.22×10-2、-27.36×10-3 等。任何小数都可以用指数形式来表示。

C语言同时支持以上两种形式的小数。但是在书写时,C语言中的指数形式和数学中的指数形式有所差异。

C语言中小数的指数形式为:

aEn 或 aen

a 为尾数部分,是一个十进制数;n 为指数部分,是一个十进制整数;Ee是固定的字符,用于分割尾数部分和指数部分。整个表达式等价于 a×10n

指数形式的小数举例:

  • 2.1E5 = 2.1×105,其中 2.1 是尾数,5 是指数。
  • 3.7E-2 = 3.7×10-2,其中 3.7 是尾数,-2 是指数。
  • 0.5E7 = 0.5×107,其中 0.5 是尾数,7 是指数。


C语言中常用的小数有两种类型,分别是 float 或 double;float 称为单精度浮点型,double 称为双精度浮点型。

不像整数,小数没有那么多幺蛾子,小数的长度是固定的,float 始终占用4个字节,double 始终占用8个字节。

小数的输出

小数也可以使用 printf 函数输出,包括十进制形式和指数形式,它们对应的格式控制符分别是:

  • %f 以十进制形式输出 float 类型;
  • %lf 以十进制形式输出 double 类型;
  • %e 以指数形式输出 float 类型,输出结果中的 e 小写;
  • %E 以指数形式输出 float 类型,输出结果中的 E 大写;
  • %le 以指数形式输出 double 类型,输出结果中的 e 小写;
  • %lE 以指数形式输出 double 类型,输出结果中的 E 大写。


下面的代码演示了小数的表示以及输出:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. float a = 0.302;
  6. float b = 128.101;
  7. double c = 123;
  8. float d = 112.64E3;
  9. double e = 0.7623e-2;
  10. float f = 1.23002398;
  11. printf("a=%e \nb=%f \nc=%lf \nd=%lE \ne=%lf \nf=%f\n", a, b, c, d, e, f);
  12. return 0;
  13. }

运行结果:
a=3.020000e-01
b=128.100998
c=123.000000
d=1.126400E+05
e=0.007623
f=1.230024

对代码的说明:
1) %f 和 %lf 默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。

2) 将整数赋值给 float 变量时会变成小数。

3) 以指数形式输出小数时,输出结果为科学计数法;也就是说,尾数部分的取值为:0 ≤ 尾数 < 10。

4) b 的输出结果让人费解,才三位小数,为什么不能精确输出,而是输出一个近似值呢?这和小数在内存中的存储形式有关,很多简单的小数压根不能精确存储,所以www.fifabo.com也就不能精确输出,我们将在下节中详细讲解。

另外,小数还有一种更加智能的输出方式,就是使用%g。%g 会对比小数的十进制形式和指数形式,以最短的方式来输出小数,让输出结果更加简练。所谓最短,就是输出结果占用最少的字符。

%g 使用示例:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. float a = 0.00001;
  6. float b = 30000000;
  7. float c = 12.84;
  8. float d = 1.229338455;
  9. printf("a=%g \nb=%g \nc=%g \nd=%g\n", a, b, c, d);
  10. return 0;
  11. }

运行结果:
a=1e-05
b=3e+07
c=12.84
d=1.22934

对各个小数的分析:

  • a 的十进制形式是 0.00001,占用七个字符的位置,a 的指数形式是 1e-05,占用五个字符的位置,指数形式较短,所以以指数的形式输出。
  • b 的十进制形式是 30000000,占用八个字符的位置,b 的指数形式是 3e+07,占用五个字符的位置,指数形式较短,所以以指数的形式输出。
  • c 的十进制形式是 12.84,占用五个字符的位置,c 的指数形式是 1.284e+01,占用九个字符的位置,十进制形式较短,所以以十进制的形式输出。
  • d 的十进制形式是 1.22934,占用七个字符的位置,d 的指数形式是 1.22934e+00,占用十一个字符的位置,十进制形式较短,所以以十进制的形式输出。


读者需要注意的两点是:

  • %g 默认最多保留六位有效数字,包括整数部分和小数部分;%f 和 %e 默认保留六位小数,只包括小数部分。
  • %g 不会在最后强加 0 来凑够有效数字的位数,而 %f 和 %e 会在最后强加 0 来凑够小数部分的位数。


总之,%g 要以最短的方式来输出小数,并且小数部分表现很自然,不会强加零,比 %f 和 %e 更有弹性,这在大部分情况下是符合用户习惯的。

除了 %g,还有 %lg、%G、%lG:

  • %g 和 %lg 分别用来输出 float 类型和 double 类型,并且当以指数形式输出时,e小写。
  • %G 和 %lG 也分别用来输出 float 类型和 double 类型,只是当以指数形式输出时,E大写。

数字的后缀

一个数字,是有默认类型的:对于整数,默认是 int 类型;对于小数,默认是 double 类型。

请看下面的例子:

  1. long a = 100;
  2. int b = 294;
  3. float x = 52.55;
  4. double y = 18.6;

100 和 294 这两个数字默认都是 int 类型的,将 100 赋值给 a,必须先从 int 类型转换为 long 类型,而将 294 赋值给 b 就不用转换了。

52.55 和 18.6 这两个数字默认都是 double 类型的,将 52.55 赋值给 x,必须先从 double 类型转换为 float 类型,而将 18.6 赋值给 y 就不用转换了。

如果不想让数字使用默认的类型,那么可以给数字加上后缀,手动指明类型:

  • 在整数后面紧跟 l 或者 L(不区分大小写)表明该数字是 long 类型;
  • 在小数后面紧跟 f 或者 F(不区分大小写)表明该数字是 float 类型。


请看下面的代码:

  1. long a = 100l;
  2. int b = 294;
  3. short c = 32L;
  4. float x = 52.55f;
  5. double y = 18.6F;
  6. float z = 0.02;

加上后缀,虽然数字的类型变了,但这并不意味着该数字只能赋值给指定的类型,它仍然能够赋值给其他的类型,只要进行了一下类型转换就可以了。

对于初学者,很少会用到数字的后缀,加不加往往没有什么区别,也不影响实际编程,但是既然学了C语言,还是要知道这个知识点的,万一看到别人的代码这么用了,而你却不明白怎么回事,那就尴尬了。

关于数据类型的转换,我们将在一节中深入探讨。

小数和整数相互赋值

在C语言中,整数和小数之间可以相互赋值:

  • 将一个整数赋值给小数类型,在小数点后面加 0 就可以,加几个都无所谓。
  • 将一个小数赋值给整数类型,就得把小数部分丢掉,只能取整数部分,这会改变数字本来的值。注意是直接丢掉小数部分,而不是按照四舍五入取近似值。


请看下面的代码:

  1. #include <stdio.h>
  2. int main(){
  3. float f = 251;
  4. int w = 19.427;
  5. int x = 92.78;
  6. int y = 0.52;
  7. int z = -87.27;
  8. printf("f = %f, w = %d, x = %d, y = %d, z = %d\n", f, w, x, y, z);
  9. return 0;
  10. }

运行结果:
f = 251.000000, w = 19, x = 92, y = 0, z = -87

上一篇:float浮动导致父元素坍塌


下一篇:U3D性能优化之Lua与C#交互