「深入浅出C语言」理解取整、取余和取模

C/C++
360
0
0
2024-01-08

关于 C语言 的 取模运算 ,也许你只了解正数取模,而从未接触过负数取模,本文就来分享一波C语言取整、取模和取余的个人学习经验与心得,希望对你有所帮助。

笔者水平有限,难免存在纰漏,欢迎指正交流。

关于取整

你真的了解取整么?那你知道有几种取整方式吗?

除了最常见的向0取整以外其实是是有很多的取整方式的。

向0取整

C语言中整数除法就是遵循这一取整方式。下面代码中浮点数字面量拷贝赋值时发生了隐式类型转换,向0取整。

 # include <stdio.h>
int main()
{
    int i = -.9;
    int j =.9;
     printf ("%dn", i); //结果是-2
    printf("%dn", j); //结果是
    
    return;
} 

添加图片注释,不超过 140 字(可选)

有一个trunc取整函数(C99),同作用。

比如:

 #include<math.h>
#include<stdio.h>
int main()
{
    int i = -.9;
    int j =.9;
    printf("%fn", trunc(i)); //结果是-
    printf("%fn", trunc(j)); //结果是
    
    return;
} 

floor取整

本质是向-∞取整,注意输出格式要不然看不到结果,比如:

 #include <stdio.h>
#include <math.h> //因为使用了floor函数,需要添加该头文件
int main()
{
    printf("%.fn", floor(-2.9)); //结果是-3
    printf("%.fn", floor(-2.1)); //结果是-3
    printf("%.fn", floor(2.9)); //结果是2
    printf("%.fn", floor(2.1)); //结果是2
    
    return;
} 

ceil取整

本质是向+∞取整,注意输出格式要不然看不到结果,比如:

 #include <stdio.h>
#include <math.h>
int main()
{
    printf("%.fn", ceil(-2.9)); //结果是-2
    printf("%.fn", ceil(-2.1)); //结果是-2
    printf("%.fn", ceil(2.9)); //结果是3
    printf("%.fn", ceil(2.1)); //结果是3
    
    return;
} 

round取整

本质是四舍五入取整,比如:

 #include <stdio.h>
#include <math.h>
int main()
{
    printf("%.fn", round(2.1));//结果是2
    printf("%.fn", round(2.9));//结果是3
    printf("%.fn", round(-2.1));//结果是-2
    printf("%.fn", round(-2.9));//结果是-3
    
    return;
} 

汇总例子

 #include <stdio.h>
#include <math.h>
int main()
{
    const char * format = "%.f t%.1f t%.1f t%.1f t%.1fn";
    printf("valuetroundtfloortceilttruncn");
    printf("-----t-----t-----t----t-----n");
    printf(format,.3, round(2.3), floor(2.3), ceil(2.3), trunc(2.3));
    printf(format,.8, round(3.8), floor(3.8), ceil(3.8), trunc(3.8));
    printf(format,.5, round(5.5), floor(5.5), ceil(5.5), trunc(5.5));
    printf(format, -.3, round(-2.3), floor(-2.3), ceil(-2.3), trunc(-2.3));
    printf(format, -.8, round(-3.8), floor(-3.8), ceil(-3.8), trunc(-3.8));
    printf(format, -.5, round(-5.5), floor(-5.5), ceil(-5.5), trunc(-5.5));
    
    return;
} 

「深入浅出C语言」理解取整、取余和取模

接下来深度理解取余/取模运算。

关于取余和取模

你知道2/(-2),2%(-2)的值分别是多少吗?我们一点点来看。

取模运算的定义与引例

给出一个定义: 如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ r < d。其中,q被称为商,r 被称为余数,取模运算求取的就是这个余数r。 举个例子:

 int main()
{
    int a =;
    int b =;
    
    printf("%dn", a % b);//a = q*d + r => = 3*3 + 1
    return;
} 

结果显而易见会是1,那如果改成下面这样结果又会如何?

 int main()
{
    int a = -;//改成负数了
    int b =;
    
    printf("%dn", a % b);
    return;
} 

而在 python 中结果却是完全不一样的:

 >>> print(-%3)

>>> 

分析

很显然,前面关于取模运算的定义并不能较好地满足这两门语言上的取模运算。

因为在C语言中,-10%3出现了负数,而根据定义是应该满足 a = q*d + r 且0 ≤ r < d的,这就说明C语言中取模运算得到的余数,是不满足我们所谓的取模定义的——出现了r<0的情况。

故有了一个修订版的定义:

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r|< |d|。其中,q 被称为商,r 被称为余数,取模运算求取的就是这个余数r。 有了这个新的定义,那么C或者Python中的取模运算,就都能解释了。

解释C: -10 = (-3) * 3 + (-1)

解释Python:-10 = (?)* 3 + 2,其中,可以推导出来(?)必须是-4,即-10 = (-4)* 3 + 2,才能满足定义,这也和下面-10//3的结果吻合。

 >>>print(-//3)
-
>>> 

所以,在不同语言,同一个计算表达式,负数取模运算结果是不同的。我们可以分别叫做正余数和负余数。

由上面的例子可以看出,具体余数r的大小,本质是取决于商q的。

而商,又取决于谁呢?取决于除法计算的时候的取整规则。

比如:在C中,10/(-3)的值向0取整,所以得到-3;而在python中,10/(-3)的值向-∞取整,所以得到-4

取余和取模一样吗

注意我前面说的是取模运算,接下来才是取余和取模。

并不是完全严格等价的,虽然大部分情况下差不多(用的都是正数)。

取余或者取模,都应该要算出商,然后才能得出余数。

我们这里主要发掘一下二者的区别。

本质 1 取整

取余 :尽可能让商进行向0取整。

取模 :尽可能让商进行向-∞取整。

结合上面讲的例子,可知:

C中%,本质其实是取余。

Python中%,本质其实是取模。

操作数 是正还是负是有差别的:

对任何一个大于0的数,对其进行向0取整和向-∞取整,取整方向是一致的,故此时取模等价于取余。

对任何一个小于0的数,对其进行向0取整和向-∞取整,取整方向是相反的,故此时取模不等价于取余。

本质 2 符号

参与取模运算的两个数据,如果同符号,取模等价于取余。

因为同符号数据相除,得到的商,一定是正数,即大于0!故在对其商进行取整的时候,取模等价于取余。

那如果参与运算的数据符号不同呢?比如:

 int main()
{
    printf("%dn", - / 3); //结果:-3
    printf("%dnn", - % 3); //结果:-1  因为-10=(-3)*3+(-1)
    
    printf("%dn", / -3); //结果:-3
    printf("%dnn", % -3); //结果:1  因为10=(-3)*(-3)+1
    return;
} 

所以可以看出:如果两数符号不同,余数的求法参考之前定义,而余数符号与被除数相同。此时取余不等价于取模,因为符号不同的数据相除得到的商一定是负数,对商取整时取模和取余提供的方向相反。

但是,在python中却是这样:

 >>> print(-//3)
-
>>> print(//-3)
-
>>> print(-%3)

>>> print(%-3)
-
>>> 

我们发现在这里余数符号与被除数的相反,why?

还记得前面讲过的规律吗?

具体余数r的大小,本质是取决于商q的。

而商,又取决于谁呢?取决于除法计算的时候的取整规则。

我们来看一个不怎么严谨的数学推导,理解一下即可:

重新看看定义:

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r|< |d|。其中,q 被称为商,r 被称为余数。

a = q*d + r 变换成 r = a – q*d 变换成 r = a + (-q*d) ,我们重点讨论的是r的符号问题。

对于: x = y + z ,这样的表达式,x的符号与|y|、|z|中更大的那一个数的符号一致,若是y和z互为相反数则x为0。

r = a + (-q*d) 中,|a|和|-q*d|谁大,取决于商q的取整方式(a和d固定 )。

C语言中是向0取整的,也就是q本身的绝对值是减小的。 如:

-10/3=-3.33… 向0取整 -3,a=-10 对应绝对值为|10|, -q*d=-(-3)*3=9 对应绝对值为|9|

10/-3=-3.33… 向0取整 -3,a=10 对应绝对值为|10|, -q*d=-(-3)*(-3)=-9 对应绝对值为|9|

所以-q*d的绝对值变小了,而且往往比a的绝对值要小,因此r的符号就取决于a的符号。

而python是向-∞取整的,也就是q本身的绝对值是增大的。 如:

-10/3=-3.33… 向-∞取整 -4,a=-10 对应绝对值为|10|, -q*d=-(-4)*3=12 对应绝对值为|12|

10/-3=-3.33… 向-∞取整 -4, a=10 对应绝对值为|10|, -q*d=-(-4)*(-3)=-12 对应绝对值为|12|

所以 -q*d 的绝对值都变大了,而且往往比a的绝对值要大,因此r的符号就取决于 -q*d 的符号,又因为我们这里一直在讨论的是不同符号的数据取余/取模,所以除数d的符号与被除数a的相反,并且得到的商q一定是负数,-q正好把 负号 抵消掉了, -q*d 的符号就取决于d了,也就是说最终r的符号取决于d的符号,同时与a的符号相反。

结论:如果参与取余的两个数据符号不同,在C语言中(或者其他采用向0取整的语言如:C++, Java ),余数符号与被除数的符号相同,而在python语言中(或者其他采用-∞取整的语言),余数符号与被除数的符号相反。

最后我们再看回最开始提到的问题:2/(-2),2%(-2)的值分别是多少?

 int main()
{
    printf("%dn", / (-2)); //-1
    printf("%dn", % (-2)); //2=(-1)*(-2)+r,r就是0
    return;
}