0.前言
您好,这里是limou3434的一篇个人博文,感兴趣的话您也可以看看我的其他文章,本次我想给您带来的是关于C语言操作符‘%’的一些奇怪现象以及背后的原理解释,本章用了一点点python语法(比如在python中“//”是整除的意思),即便您没有学过python也不影响阅读。
1.一些奇怪的现象
在编写C语言程序的时候,取余运算符‘%’的一些“奇怪”行为经常困惑着初学者。尤其是在使用其他语言的时候,‘%’的行为更是让人摸不着头脑,您可以先观察下面在Visual Studio 2022环境的和python 3.8.5环境中的‘%’的运算结果在哪里是不同的。
1.1.C语言下‘%’的运算结果
#include <stdio.h>
int main()
{
int a = -10;
int b = 3;
printf("%d\n", a / b);//结果输出-3
printf("%d\n", a % b);//结果输出-1
return 0;
}
1.2.python下‘%’的运算结果
C:\Users\DELL>python
Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> -10//3 #在python中“//”是整除的意思
-4
>>> -10%3
2
1.2.不一样的地方
为什么运行的结果不一样,明明是同一串表达式不是么!?另外我们还能发现,C语言和python不仅仅是在‘%’的运算结果上不一样,甚至‘/’的运算结果都是不一样的,这就不得不让我们好奇了:这两种语言究竟是怎么实现‘%’和‘/’计算的???
2.取余/取模的计算细节
2.1.取整模式
首先我们先了解下不同的取整模式(怎么就扯到取整了?不要担心,您且往后看看便知)不过由于我们主要探讨的语言是C语言,所以下面我们使用C语言的四个库函数来详细讲解四种取整模式(这几个库函数的头文件都是math.h)
2.1.1.向零取整(C语言的默认模式)
向零取整是什么意思呢?我们可以把所有的浮点数想象在同一根数轴上,我们为了取整,将浮点数们往0这个点“靠近”,类似学生上体育课时,体育老师发出的号令:“以中间那位同学为‘基准’进行靠拢”
在C语言中库函数trunc可以达到向零取整的效果
#include <stdio.h>
#include <math.h>
int main()
{
printf("%d %d", (int)trunc(2.9), (int)trunc(-2.9));
return 0;
}//也是输出零向取整的结果“2 -2”
2.1.2.向负无穷取整
类似向零取整,向负无穷取整也是一样的道理,只不过这一次的”基准“是看不见的“负无穷”而已
在C语言中库函数floor可以达到向负无穷取整的效果
floor(-2.9);//-3.0
floor(-2.1);//-3.0
floor(2.9);//2.0
floor(2.1);//2.0
2.1.3.向正无穷取整
类似前两种,向正无穷取整也是一样的道理,这一次的”基准“就是看不见的“正无穷”了
在C语言中库函数ceil可以达到向正无穷取整的效果
ceil(-2.9);//2.0
ceil(-2.1);//2.0
ceil(2.9);//3.0
ceil(2.1);//3.0
2.1.4.四舍五入取整
这个就比较简单了,就是大家平时熟悉的四舍五入,在C语言中库函数round可以达到四舍五入取整的效果
round(2.1);//2.0
round(2.9);//3.0
round(-2.1);//-2.0
round(-2.9);//-3.0
2.2.取余/取模的定义
2.2.1.数学中取余/取模的定义
对于整数 a 、 q 、 d 、 r ,其中 d 为非 0 ,有 a = q ∗ d + r ( 0 < = r < d ) ,其中 q 叫做商, r 叫做余数 对于整数a、q、d、r,其中d为非0,有a=q*d+r(0<=r<d),其中q叫做商,r叫做余数 对于整数a、q、d、r,其中d为非0,有a=q∗d+r(0<=r<d),其中q叫做商,r叫做余数但是这可不行啊,C语言首先就不干了,为什么呢?回顾一下在C语言中“ − 10 % 3 = − 1 -10\%3=-1 −10%3=−1”这个例子,这个r(余数)小于0了,这不就不满足定义了吗?别急,我们可以试着引入负余数的概念,修改一下定义,让C语言也符合一下这个定义
2.2.2.修改后取余/取模的定义
对于整数 a 、 q 、 d 、 r ,其中 d 为非 0 ,有 a = q ∗ d + r ( 0 < = ∣ r ∣ < ∣ d ∣ ) ,其中 q 叫做商, r 叫做余数 对于整数a、q、d、r,其中d为非0,有a=q*d+r(0<=|r|<|d|),其中q叫做商,r叫做余数 对于整数a、q、d、r,其中d为非0,有a=q∗d+r(0<=∣r∣<∣d∣),其中q叫做商,r叫做余数这样C语言的运算结果就也是符合定义的了
2.2.3.对于‘%’操作符在不同语言中的解释
- 通过第二个定义,我们会发现余数会分为正余数和负余数,而“余数的大小”取决于“商q”,“商q”则取决于除法除法的“除法规则”,在语言里“除法规则”的本质就是“取整规则”。因此会发现,余数结果的不一样实际上是语言的取整规则在作怪!
- 在厘清出一下:“除法规则”(“取整规则”)->“商q”->“余数的大小”,因此,只需要知道根据整除规则就可以得出“商q”了
- 而C语言的取整规则默认是向零取整,因此“-10/3=-3(3.333…向零取整)”,再根据公式“-10=-3*3+r”,得到“r=-1”
- python的取整规则默认是向负无穷取整,因此“-10/3=-4(3.333…向负无穷取整)”,再根据公式“-10=-4*3+r”,得到“r=2”
- 因此我们成功明白了为什么‘%’在两种语言中得出不一样结果的原因!!!
2.4.“取余”和“取模”的区别
实际上“取余”和“取模”是两个概念:
- 取余:尽可能让商进行向0取整,因此C语言的是取余,因此严格来说这个操作符%就叫“取余”
- 取模:尽可能让商进行向-∞取整,因此python的是取模,因此严格来说这个操作符%就叫“取模”
2.5.“取余”和“取模”也有相等的时候
那么为什么平时我们不会去刻意区分这两个概念,甚至是混用呢?实际上我们不难发现,只要是操作数都是正数,在进行“取余/取模”运算,无论是哪一种,得到的结果都是一样的。因为对于正数来说,“向零取整”和“向负无穷取整”的方向得到的“商q”恰好是一样的,因此在正数的时候,可以直接简单粗暴的认为 “取余” = “取模” “取余”=“取模” “取余”=“取模”,我们只需要在负数的时候区分开来就行了。
3.总结归纳
总结一下把,本次我们主要探讨了有关C语言和python语言中‘%’的区别,并且通过四种“取整模式”和“修改后的定义公式”得到“余数r”,并且揭示了为何不同语言经过‘%’操作符后所得结果不同的原因:不同语言默认的“取整模式”不同。