深度理解取余取模运算,你得知道这些...

深度理解取余取模运算,你得知道这些...,第1张

大家在日常编程或者学习中都会经常接触到取余/取模运算,那么计算机中取余/取模运算究竟是怎么定义的,读完本篇文章,相信你一点会有很大收获!

文章目录
  • step 1:你可能不知道的四种“取整”方式
    • 1.1 C语言中本质是:向0取整
    • 1.2 `floor` 函数:“地板取整”,即统一向小的方向取整。
    • 1.3 `ceil` 函数:统一向大的方向取整。
    • 1.4 `round` 函数:熟知的四舍五入取整
    • 1.5 汇总例子
  • step 2:用公式理解“取模”
    • 2.1 取模概念
    • 2.2 对“取模”修订版的定义
    • 2.3 结论
  • step 3:“取余”和“取模”一样吗?
    • 3.1 本质1: 取整
    • 3.2 本质2: 符号
  • step 4:如果参与运算的数据是不同符号的
  • step 5:总结

step 1:你可能不知道的四种“取整”方式

首先谈谈关于数学取整的问题

1.1 C语言中本质是:向0取整
#include  
#include
int main() 
{ 
    printf("%d\n", 2.9); //结果是:2 
    printf("%d\n", -2.9); //结果是:-2 
    
    //trunc取整函数,同作用。
    printf("%d\n", (int)turnc(2.9)); //结果是:2 
    printf("%d\n", (int)trunc(-2.9)); //结果是:-2 
    return 0; 
}

1.2 floor 函数:“地板取整”,即统一向小的方向取整。
#include  
#include  //因为使用了floor函数,需要添加该头文件 

int main() 
{ 
    //本质是向-∞取整,注意输出格式要不然看不到结果 
    printf("%.1f\n", floor(-2.9)); //-3.0
    printf("%.1f\n", floor(-2.1)); //-3.0
    printf("%.1f\n", floor(2.9));  //2.0
    printf("%.1f\n", floor(2.1));  //2.0
    return 0; 
}

1.3 ceil 函数:统一向大的方向取整。
#include  
#include  

int main() 
{ 
    //本质是向+∞取整,注意输出格式要不然看不到结果 
    printf("%.1f\n", ceil(-2.9)); //-2.0
    printf("%.1f\n", ceil(-2.1)); //-2.0
    printf("%.1f\n", ceil(2.9));  //3.0
    printf("%.1f\n", ceil(2.1));  //3.0
    return 0; 
} 

1.4 round 函数:熟知的四舍五入取整
#include  
#include  

int main() 
{ 
    //本质是四舍五入 
    printf("%.1f\n", round(2.1));  //2.0
    printf("%.1f\n", round(2.9));  //3.0
    printf("%.1f\n", round(-2.1)); //-2.0
    printf("%.1f\n", round(-2.9)); //-3.0
    return 0;
}
1.5 汇总例子
#include  
#include  

int main() 
{ 

    const char * format = "%.1f \t%.1f \t%.1f \t%.1f \t%.1f\n"; 
    printf("value\tround\tfloor\tceil\ttrunc\n"); 
    printf("-----\t-----\t-----\t----\t-----\n"); 
    printf(format, 2.3, round(2.3), floor(2.3), ceil(2.3), trunc(2.3)); 
    printf(format, 3.8, round(3.8), floor(3.8), ceil(3.8), trunc(3.8)); 
    printf(format, 5.5, round(5.5), floor(5.5), ceil(5.5), trunc(5.5)); 
    printf(format, -2.3, round(-2.3), floor(-2.3), ceil(-2.3), trunc(-2.3)); 
    printf(format, -3.8, round(-3.8), floor(-3.8), ceil(-3.8), trunc(-3.8)); 
    printf(format, -5.5, round(-5.5), floor(-5.5), ceil(-5.5), trunc(-5.5)); 
    return 0; 
} 

结论:浮点数(整数/整数),是有很多的取整方式的。具体使用哪种方式,取决于具体情况。

step 2:用公式理解“取模” 2.1 取模概念

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

//对应代码 
#include  

int main() 
{ 
    int a = 10; 
    int d = 3; 
    printf("%d\n", a%d); //结果是1 
    return 0; 
}

因为:a=10,d=3,q=3,r=1 0<=r

所以:a = q*d+r -> 10=3*3+1

如果是下面的代码呢?

#include  

int main() 
{ 
    int a = -10;
    int d = 3; 
    //printf("%d\n", a/d); //C语言中是-3,很好理解 
    printf("%d\n", a%d);   //-1
    return 0; 
}

  • gcc 编译打印 -1
  • python中,print(-10%3)会打印 2

结论:很显然,上面关于取模的定义,并不能满足语言上的取模运算

因为在C中,现在 -10%3 出现了负数,根据定义:满足 a = q*d + r 且0 ≤ r < d,C语言中的余数,是不满足定义的,

因为,r<0

2.2 对“取模”修订版的定义

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

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

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

解释 :Python:-10 = (?)* 3 + 2,其中,可以推到出来,'?'必须是 -4 。即 -10 = (-4)* 3 + 2,才能满足定义。

所以,在不同语言,同一个计算表达式,负数“取模”结果是不同的。我们可以称之为分别叫做正余数 和 负余数,上面的(-1)就是负余数,python里的2就是正余数。

2.3 结论

由上面的例子可以看出,具体余数 r 的大小,本质是取决于商 q 的。 总结来说就是余数为多少取决于商为多少。

而商取决于除法计算的时候,所使用的取整规则。

C : -10/3 -> -3 -> 商向0取整

python: -10 -> -4 -> 商向-∞取整

step 3:“取余”和“取模”一样吗?

大家应该看到了,上面的取模都是带着""的。说明这两个并不能严格等价(虽然大部分情况差不多)

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

3.1 本质1: 取整

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

取模:尽可能让商,向-∞方向取整。

故:

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

理解链:

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

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

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

3.2 本质2: 符号

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

//计算数据同符号 
#include  

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

Python 3.7

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

注意:python中 / 默认是浮点数除法, // 才是整数除法,并进行 -∞ 取整

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

step 4:如果参与运算的数据是不同符号的
#include 

int main() 
{ 
    //a = q*d + r
    printf("%d\n", -10 / 3); //结果:-3 向0取整
    printf("%d\n\n", -10 % 3); //结果:-1 为什么? -10=(-3)*3+(-1) 
    printf("%d\n", 10 / -3); //结果:-3 
    printf("%d\n\n", 10 % -3); //结果:1 为什么?10=(-3)*(-3)+1
    return 0; 
}

明显结论:如果不同符号,余数(r)的求法,参考之前定义,确定商后决定余数。而余数符号,与被除数相同

这是真的吗?

>>> print(-10//3) 
-4
>>> print(10//-3) 
-4
>>> print(-10%3)
2
>>> print(10%-3)
-2

在python里余数符号,与被除数正好相反,为什么呢?

重新看看定义:

如果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)

对于:x = y + z,这样的表达式我们可以这么理解:x的符号 与 |y|、|z|中大的数据一致

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


c是向0取整的,也就是 q 本身的绝对值是减小的。

如:

-10/3=-3.333.33 向0取整 -3. a=-10 |10|, -q*d=-(-3)*3=9 |9|

10/-3=-3.333.33 向0取整 -3. a=10 |10|, -q*d=-(-3)*(-3)=-9 |9|

绝对值都变小了


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

-10/3=-3.333.33 '//'向-∞取整 -4. a=-10 |10|, -q*d=-(-4)*3=12 |12|

10/-3=--3.333.33 '//'向-∞取整 -4. a=10 |10|, -q*d=-(-4)*(-3)=-12 |12|

绝对值都变大了


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

step 5:总结
  • 浮点数(或者整数相除),是有很多的取整方式的,0向取整、(正、负)无穷向取整、四舍五入取整…。(step 1证明)
  • 如果 a 和 d 是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r|< |d|。其中,q 被称为商,r 被称为余数。(step 2证明)
  • 在不同语言,同一个计算表达式,“取模”结果是不同的。我们可以称之为分别叫做正余数 和 负余数(step 2证明)
  • 具体余数 r 的大小,本质是取决于商 q 的。而商,又取决于除法计算的时候,取整规则。(step 2证明)
  • 取余 vs 取模:取余是尽可能让商,进行向 0 取整。取模是尽可能让商,向 -∞ 方向取整。(step 3证明)
  • 参与取余的两个数据,如果同符号,取模等价于取余(step 3证明)
  • 如果参与取余的两个数据符号不同,在C语言中(或者其他采用向0取整的语言如:C++,Java),余数符号,与被除数相同。(因为采用的向0取整)(step 4证明)

以上就是关于计算机”取模/取余“的全部内容,相信大家看到这里应该也对取模/取余有了新的看法,很高兴能够为大家带来帮助,码字不易,如果大家觉得我这篇文章有帮助的话,可以👍支持一下。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/794290.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)