不同语言的负数取余问题
问题的出现
偶然在leetcode上看到一道这样的题:
Given a 32-bit signed integer, reverse digits of an integer.
翻译成中文就是:
给定一个32位有符号整数,将整数由低位到高位反向输出,例:
输入:1230
返回:321
题目很简单,有很多种实现方式,大概十分钟左右就在脑海中想到了一个自认为最好的解法,之后用电脑实验:
int reverse_num(int x,int res)
{
if(!x) return res;
return reverse_num(x/10,(res*10+x%10));
}
int main()
{
int val=0;
val=reverse_num(-9870,val);
cout<
}
输出结果:
-789
解决!!其实用循环也可以高效地实现,为什么要用递归?因为递归总是能写出简洁优美的代码(其实是为了装X...)。
作为习惯,我再用python实现一遍,同样的代码结构:
def reverse(x,res):
if x==0:
return res
return reverse(x/10,(res*10+x%10))
def main():
num=-9870
res=0
val=reverse(num,res)
print val
输出结果:
RuntimeError: maximum recursion depth exceeded
What the ****!!??
我抬起我颤抖的小手移动着屏幕一行一行地检查代码,发现并没有错。
本以为大局已定,结果被极限反杀,当然不能就这么算了,于是开启debug模式
程序毕竟简单,很快就发现了问题:
>>> print -987/10
-99
>>> print -99/10
-10
>>> print -10/10
-1
>>> print -1/10
-1
>>> print -1/10
-1
>>> print -1/10
-1
到这里,各位观众老爷们应该也看出问题来了,从上述运行结果来看,-987/10的结果居然是-99而不是-98,-1/10的结果是-1,再次执行-1/10的结果当然又是是-1,而递归的退出条件为x=0,这导致递归无限执行,所以栈溢出,对于-1/10为什么等于-1这个结果,当然是既震惊又好奇,接下来便是一探究竟!!
问题的解决
根据资料显示,目前使用的主流除法处理有两种:Truncated Division和Floored Division,这两种除法在对正数除法的处理上是一致的,而在对负数处理上则略有不同。
首先,需要了解的是,对于整数的除法,都满足这个公式:
m=q*n+r
m为被除数
n为除数
r为余
q为商
m,n,q,r都为整数
即:
14=3*4+2
14除以4得3余2
这不是很标准了么,那为什么还会有分歧呢?
正整数的除法当然是没有问题的,但是如果遇到负数的除法,比如
-8/5
结果可以是两种,即:
-8 = 5*(-1)+(-3)
在这种情况下,商为-1,余数为-3
或者:
-8 = 5*(-2)+2
而在这种情况下,商为-2,余数为2
这两种除法的分歧导致了上述的不同语言的不同标准。
官方标准
根据官方资料显示,在C89和C98的标准中,对此并没有做规定,把实现留给了编译器来决定,这会导致什么结果呢?就是我们常说的实践出真知在这种情况下可能会得到一个错误的结果!
不管你的编译器用的是C/C++类标准还是python类标准,你得出的结论就是单一标准,写出来的代码在另一个编译器下并不具有移植性。
思考
由此引发博主的一个思考:有时候在研究这类计算机问题时,我们不能单单以某个平台上的实验结果作为标准答案,这是有所偏颇的,编译工具链(脚本解释器)常常有多个版本,而单一平台却无法覆盖所有编译器(脚本解释器)版本,可能仅仅是选择了最通用的版本或者是从多个分歧版本中选其一。
统一标准
在C99的标准中,明确规定了"truncation toward zero",即向0取整。在这个模式下,负整数除法中,商为负数时,小数部分是往靠近0的方向进行取整,即舍弃小数部分,C++和Java则沿用了C的方式,还是那个例子:
-8/5=-1.6
商为-1.6,但是因为是整数除法,小数部分向0取整,商为-1,所以余数为-3,即:
-8 = 5*(-1)+(-3)
而在python中,应用的则是小数部分向进1的方向取整,举个例子:
-8/5=-1.6
商为-1.6,但是因为是整数除法,小数部分向进1取整,商为-2,所以余数为2,即:
-8 = 5*(-2)+2
对于两种不同语言的除法实现,我们已经有了基本的了解,但是事情就这样结束了吗?并没有!!
上述讨论的仅仅是
正整数/正整数
负整数/正整数
还有两种情况怎么能漏呢?
负整数/负整数
正整数/负整数
对于负整数/负整数的除法,这两种除法有没有区别呢?
既然在C99之后对于C带符号整数除法的标准中已经统一,我们还是可以选择用上机运行代码的方式来检验
正整数/负整数
C++代码片段:
int div = 8/-5;
int mod = 8%-5;
int div1= 5/-8;
int mod1= 5%-8;
cout<
cout<
运行结果:
8/-5=-1
8%-5=3
5/-8=0
5%-8=5
python代码片段:
print "%s %d" %("8/-5=",8/-5)
print "%s %d" %("8%-5=",8%-5)
print "%s %d" %("5/-8=",5/-8)
print "%s %d" %("5%-8=",5%-8)
运行结果:
8/-5=2
8%-5=-2
5/-8=-1
5%-8=-3
在正整数/负整数的示例中,C/C++和python的标准如上面所述,商为负数时,商的小数部分一个舍一个入,导致的结果也不一样。
负整数/负整数
C/C++代码片段:
int div = -8/-5;
int mod = -8%-5;
int div1= -5/-8;
int mod1=-5%-8;
cout<
cout<
运行结果:
-8/-5=1
-8%-5=-3
-5/-8=0
-5%-8=-5
python代码片段:
print "%s %d" %("-8/-5=",-8/-5)
print "%s %d" %("-8%-5=",-8%-5)
print "%s %d" %("-5/-8=",-5/-8)
print "%s %d" %("-5%-8=",-5%-8)
运行结果:
-8/-5=1
-8%-5=-3
-5/-8=0
-5%-8=-5
两种语言的输出结果是一样的,很多盆友就开始有点懵了,不是说标准不一样,小数部分取整的方向不一样吗?
如果你仔细看上面的例子,就会发现一个前提条件,商为负数时,取整有差异,而这里商为正数,如-5/-8=0.625,则按照正整数的除法的统一规则(别忘了!正整数的处理两种语言是一致的):
商取0,则:
-5 = 0*(-8)+(-5)
余数为-5
简单总结
python和C/C++/JAVA在商为负数的除法处理上有两种标准,在python中,商的小数部分进位,而在C/C++/JAVA中,商的小数部分被舍弃。(网上资料显示Ruby的处理和python一个标准,博主未进行测试,有兴趣的小伙伴可以尝试一下)
好了,关于不同语言的有符号整数除法的讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言
原创博客,转载请注明出处!
祝各位早日实现项目丛中过,bug不沾身.