每当我需要为二进制搜索等算法平均两个数字时,我总是这样做:
int mid = low + ((high - low) / 2);
我最近在this post看到了另一种方法,但我不明白。它说你可以用Java做到这一点:
int mid = (low + high) >>> 1;
或在C ++中的这个:
int mid = ((unsigned int)low + (unsigned int)high)) >> 1;
C ++版本实质上使两个操作数都无符号,因此执行移位会导致算术移位而不是有符号移位。我理解这两段代码正在做什么,但是这如何解决溢出问题呢?我认为整个问题是中间值high + low
可能会溢出?
修改
哦,呃。所有的答案并没有完全回答我的问题,但是@John Zeringue的答案让它点击了。我试着在这里解释一下。 Java中(high + low)/2
的问题并不完全是high + low
溢出(它会溢出,因为整数都是有符号的,但所有位仍然存在,并且没有信息丢失)。像这样取平均值的问题是分裂。该部门以签名值运行,因此您的结果将为负数。相反,使用移位将除以2但考虑位而不是符号(有效地将其视为无符号)。
答案 0 :(得分:6)
所以让我们考虑字节而不是整数。唯一的区别是一个字节是一个8位整数,而一个int有32位。在Java中,两者都是有符号的,这意味着前导位表示它们是正数(0)还是负数(1)。
byte low = Byte.valueOf("01111111", 2); // The maximum byte value
byte high = low; // This copies low.
byte sum = low + high; // The bit representation of this is 11111110, which, having a
// leading 1, is negative. Consider this the worst case
// overflow, since low and high can't be any larger.
byte mid = sum >>> 1; // This correctly gives us 01111111, fixing the overflow.
对于整数,这是一回事。基本上所有这一点的要点是在有符号整数上使用无符号位移允许您利用前导位来处理低和高的最大可能值。
答案 1 :(得分:5)
您看到的代码已损坏:它无法正确计算负数的平均值。如果你只使用非负值,比如索引,那就没关系,但它不是一般的替代品。 你原来的代码,
int mid = low + ((high - low) / 2);
不会因溢出而安全,因为差异high - low
可能会溢出有符号整数的范围。同样,如果你只使用非负整数,那就没关系。
使用A+B = 2*(A&B) + A^B
我们可以计算两个整数的平均值而没有溢出的事实:
int mid = (high&low) + (high^low)/2;
您可以使用位移计算除以2,但请记住两者不相同:除法向0舍入,而位移总是向下舍入。
int mid = (high&low) + ((high^low)>>1);
答案 2 :(得分:2)
C ++版本有一个隐藏的作弊:low
和high
是int
,但它们永远不会消极。当你将它们转换为unsigned int
时,你的符号位变成一个额外的精度位,一次加法就不会溢出。
这不是一个很好的作弊,因为无论如何数组索引应该是unsigned
。
就像其他地方所说的那样,i >> 1
对于无符号整数意味着/2
。
答案 3 :(得分:1)
C ++版没有解决溢出问题。它只解决了使用shift而不是/
成功除以2的问题,这是编译器应该能够自我实现的优化,如果它会提高性能。
另一方面,如果你的积分类型足够大以保持合理的指数范围,溢出可能不是真正的问题。
答案 4 :(得分:0)
您不能在Java中使用unsigned int。在溢出的情况下,考虑低32位,并且丢弃高位。无符号右移将帮助您将int视为unsigned int。但是,在C ++中你不会有溢出。
答案 5 :(得分:0)
通过使用您已经使用的方式,您可以安全地避免整数溢出,即:
int mid = low + ((high - low) / 2);
如果需要,让编译器完成这项工作。
答案 6 :(得分:0)
程序综合技术似乎可以解决此类问题。
在此视频中,程序员指定了约束a)无溢出,b)无除法和c)无if-then-else。合成器会自动提供漂亮的东西。