背景
项目中遇到一个奇怪的问题,定位bug半天,发现同样的负数取余操作,在不同编程语言中实现竟然不一致,我滴个天,惊呆了。于是,仔细分析了下背后原因,小结如下。
问题说明
为便于说明,先举个实际例子:请你回答下,在C和Python语言下,-1 % 8
运算结果各自是多少?
实际运行结果是:
-1 % 8 => -1 // c
-1 % 8 => 7 // python
而我们预期的希望是结果在0-7
中循环,不希望出现负数。那么C实现如何修改呢?
(-1 + 8) % 8 => 7 // c
原理分析
计算机中的除法实现,**同号相除无问题,异号相除差异大,**要有所注意。
核心原因是异号除法各家定义不同,如java/c++/c,偏向使商最大,而python等新兴语言,则偏向使商最小。以下为除法取余的标准定义:
整除:a / d = q
取余:a % d = r
需满足:a = q * d + r, 且0 ≤ |r| < |d|。
在场景-1 % 8
中,C语言实现: 商q为0,余数r为-1,满足自然数取模求余的定义。Python语言实现:商为-1,余数为7,也满足整数求余的定义,但Python能保证余数始终为0-7的正数。就C和Python语言的商而言,因为-1小于0,所以C语言的商更大。
若要使C语言也满足Python的类似输出,可以修改代码a % d
为:(a + d) % d
, 即可保证取余无负数。
进一步的,C语言中调整还有两种方案:
- 粗调:(d - 1 + 8) % 8
- 细调:(d + 1) % 8
粗调之所以加8,是为了保证d=0时,取余结果为7而非-1,保证余数都为正,且不断循环。d-1会导致结果可能为负,产生异号除法问题;而细调中,d+1则不会有这种问题,d=0时取余为1,向后偏移。所以,简化来看,前后两个区别只在于±1。
参考资料
- 实数范围内的求模(求余)运算:负数求余究竟怎么求, link