负数如何取模?深度理解取模:取余运算

文章目录

  • 关于”取整“,你必须得知道的事儿
    • C语言采取的取整规则
    • 取整函数trunc
    • 取整函数ceil
    • 取整函数floor
    • 取整函数round
    • 结论
  • 是时候该聊聊取模和取余了
  • 那么是什么造成了C和python这种现象的呢?
  • 取余和取模一样吗?
  • 如果参与运算的数据,不同符号呢?
  • 总结

在了解取模和取余运算之前,我们必须先了解 取整,因为余数是先确定好整数商之后的附带产物,而取模和取余的概念又非常相似(没错,取模和取余是不同的)

关于”取整“,你必须得知道的事儿

C语言采取的取整规则

#include 
int main() {

    int a = 2.9;
    int b = -2.9;
    printf("%d\n", a);
    printf("%d\n", b);
    return 0;
}

负数如何取模?深度理解取模:取余运算_第1张图片

结果是2和-2,大家应该是知道的,但是大家了解原理吗?看下面的图

负数如何取模?深度理解取模:取余运算_第2张图片

C语言采取的就是上图所述的向0取整

取整函数trunc

取整函数trunc采用的也是向0取整规则

#include 
#include 
int main() {
    printf("%.1f\n", trunc(-2.1));
    printf("%.1f\n", trunc(-2.9));
    printf("%.1f\n", trunc(2.1));
    printf("%.1f\n", trunc(2.9));
    return 0;
}

负数如何取模?深度理解取模:取余运算_第3张图片

取整函数ceil

#include 
#include 
int main() {
    printf("%.1f\n", ceil(-2.1));
    printf("%.1f\n", ceil(-2.9));
    printf("%.1f\n", ceil(2.1));
    printf("%.1f\n", ceil(2.9));
    return 0;
}

负数如何取模?深度理解取模:取余运算_第4张图片

结果好像都变大了

负数如何取模?深度理解取模:取余运算_第5张图片

那是因为ceil函数取整采用的规则是向+∞取整

取整函数floor

#include 
#include 
int main() {
    printf("%.1f\n", floor(-2.1));
    printf("%.1f\n", floor(-2.9));
    printf("%.1f\n", floor(2.1));
    printf("%.1f\n", floor(2.9));
    return 0;
}

负数如何取模?深度理解取模:取余运算_第6张图片

结果又全都变小了

负数如何取模?深度理解取模:取余运算_第7张图片

因为floor函数取整采取的是向-∞取整

取整函数round

#include 
#include 
int main() {
    printf("%.1f\n", round(-2.1));
    printf("%.1f\n", round(-2.9));
    printf("%.1f\n", round(2.1));
    printf("%.1f\n", round(2.9));
    return 0;
}

负数如何取模?深度理解取模:取余运算_第8张图片

这结果有点像四舍五入

负数如何取模?深度理解取模:取余运算_第9张图片

没错,round函数取整采取的是四舍五入取整

结论

浮点数(整数/整数),其实是有很多种取整方式的(四种),而C语言采取的是向0取整

是时候该聊聊取模和取余了

先看一下取模的定义:

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

看下面代码:

#include 
int main()
{
    int a = 10;
    int b = 3;
    printf("%d\n", a / b);
    printf("%d\n", a % b);
    return 0;
}

负数如何取模?深度理解取模:取余运算_第10张图片

可以看到结果分别是3和1,此时还没有任何的问题。

#include 
int main()
{
    int a = -10;
    int b = 3;
    printf("%d\n", a / b);
    printf("%d\n", a % b);
	return 0;
}

负数如何取模?深度理解取模:取余运算_第11张图片

再看结果,分别是-3和-1,这里有一点不对劲儿了,-3还好理解,但是-1好像跟定义不太符合,再继续往下看

负数如何取模?深度理解取模:取余运算_第12张图片

在python中,正数的“取模”是1非常正确,但是负数的“取模”竟然是2,这好像太不对劲儿了吧,但是这个2是符合定义的。

从这里可以得出一点结论:关于取模的定义,并不能满足语言上的取模运算

因为在C语言中,余数出现了负数,这显然是不符合定义的,所以现在这里有了一个修订版的定义:

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

有了这个新的定义,那么C中和python中的“取模”,就都能解释了。

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

解释python:-10 = (-4) * 3 + 2

那么是什么造成了C和python这种现象的呢?

我们一定要知道一点,余数一定是根据商来求得的,余数的变化随商的变化而变化。

那么上面C和python余数的不同肯定是由于商的不同而造成的,上图中也可以看出,C中负数的商是-3而python负数的商却是-4,那么这是为什么呢?

因为C和python遵守的取整规则不同,C遵守的是向0取整,而python遵守的是向-∞取整

取余和取模一样吗?

取余和取模并不能严格等价(虽然大部分情况差不多)

先看取整

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

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

所以

C中%,本质是取余

python中%,本质才是取模

理解链:

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

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

而同符号数据得到的商一定是正数,所以此时取模也等价于取余。

负数如何取模?深度理解取模:取余运算_第13张图片

负数如何取模?深度理解取模:取余运算_第14张图片

通过对比实验,更加验证了,参与取余的两个数据,如果同符号,取模等价于取余。

如果参与运算的数据,不同符号呢?

#include 
int main()
{
    printf("%d\n", -10 / 3);
    printf("%d\n", 10 / -3);
    printf("%d\n", -10 % 3);
    printf("%d\n", 10 % -3);
    return 0;
}

负数如何取模?深度理解取模:取余运算_第15张图片

通过结论可以看到:如果不同符号,参考之前的定义,余数的符号是和被除数的符号相同的。

然而真的是这样吗?

负数如何取模?深度理解取模:取余运算_第16张图片

?????

这是怎么回事?

上面讲过了C语言中的求整规则是向0取整,而python中的取整规则是向负∞取整。

其实导致上图的二者之间的余数的符号的区别,也是由于C语言和python采取的取整规则不同。

-10 // 3 = -4 ,而-4*3=-12,所以余数是-10 - (-12) = 2,

10 // -3 = -4 ,而-4*-3=12,所以余数是10 - 12 = -2

结论:如果参与取余的两个数的符号不同,在采取向0取整的语言中(C语言、C++、Java),余数符号和被除数相同;而在采取向负∞取整的语言中(python),余数符号相对是无规则的,具体符号可根据公式倒退出来。

总结

  • 浮点数(或者整数相除),是有很多的取整方式的。
  • 如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数q和r,满足a = q * d + r,q为整数,且0 <= |r| < |d|。其中,q被称为商,r被称为余数。
  • 在不同语言中,同一个计算表达式,“取模”结果是不同的。
  • 具体余数r的大小,本质是取决于商q的。而商,又取决于除法计算的时候的取整规则。
  • 取余vs取模:取余尽可能让商进行向0取整。取模尽可能让商进行向-∞取整。
  • 参与取余的两个数据,如果同符号,取模等价于取余。
  • 如果参与取余的两个数的符号不同,在采取向0取整的语言中(C语言、C++、Java),余数符号和被除数相同;而在采取向负∞取整的语言中(python),余数符号相对是无规则的,具体符号可根据公式倒退出来。

你可能感兴趣的:(c语言,c++,算法)