java if (a - b < 0) 和 if (a < b) 的区别
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33147339/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Difference between if (a - b < 0) and if (a < b)
提问by dejvuth
I was reading Java's ArrayList
source code and noticed some comparisons in if-statements.
我正在阅读 Java 的ArrayList
源代码并注意到 if 语句中的一些比较。
In Java 7, the method grow(int)
uses
在 Java 7 中,该方法grow(int)
使用
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
In Java 6, grow
didn't exist. The method ensureCapacity(int)
however uses
在 Java 6 中,grow
不存在。ensureCapacity(int)
然而该方法使用
if (newCapacity < minCapacity)
newCapacity = minCapacity;
What was the reason behind the change? Was it a performance issue or just a style?
改变背后的原因是什么?这是性能问题还是只是一种风格?
I could imagine that comparing against zero is faster, but performing a complete subtraction just to check whether it's negative seems a bit overkill to me. Also in terms of bytecode, this would involve two instructions (ISUB
and IF_ICMPGE
) instead of one (IFGE
).
我可以想象与零进行比较会更快,但是执行完全减法只是为了检查它是否为负数对我来说似乎有点矫枉过正。同样在字节码方面,这将涉及两条指令(ISUB
和IF_ICMPGE
)而不是一条(IFGE
)。
回答by Tunaki
a < b
and a - b < 0
can mean two different things. Consider the following code:
a < b
并且a - b < 0
可能意味着两件不同的事情。考虑以下代码:
int a = Integer.MAX_VALUE;
int b = Integer.MIN_VALUE;
if (a < b) {
System.out.println("a < b");
}
if (a - b < 0) {
System.out.println("a - b < 0");
}
When run, this will only print a - b < 0
. What happens is that a < b
is clearly false, but a - b
overflows and becomes -1
, which is negative.
运行时,这只会打印a - b < 0
. 发生的事情a < b
显然是错误的,但a - b
溢出并变成-1
,这是负数。
Now, having said that, consider that the array has a length that is really close to Integer.MAX_VALUE
. The code in ArrayList
goes like this:
现在,话虽如此,请考虑数组的长度非常接近Integer.MAX_VALUE
。里面的代码ArrayList
是这样的:
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
oldCapacity
is really close to Integer.MAX_VALUE
so newCapacity
(which is oldCapacity + 0.5 * oldCapacity
) might overflow and become Integer.MIN_VALUE
(i.e. negative). Then, subtracting minCapacity
underflowsback into a positive number.
oldCapacity
真的很接近Integer.MAX_VALUE
所以newCapacity
(即oldCapacity + 0.5 * oldCapacity
)可能会溢出并变为Integer.MIN_VALUE
(即负数)。然后,将minCapacity
下溢减去回正数。
This check ensures that the if
is not executed. If the code were written as if (newCapacity < minCapacity)
, it would be true
in this case (since newCapacity
is negative) so the newCapacity
would be forced to minCapacity
regardless of the oldCapacity
.
此检查可确保if
不执行。如果代码被写成if (newCapacity < minCapacity)
,这将是true
在这种情况下(因为newCapacity
是负的),因此newCapacity
将被迫minCapacity
不管oldCapacity
。
This overflow case is handled by the next if. When newCapacity
has overflowed, this will be true
: MAX_ARRAY_SIZE
is defined as Integer.MAX_VALUE - 8
and Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0
is true
. The newCapacity
is therefore rightly handled: hugeCapacity
method returns MAX_ARRAY_SIZE
or Integer.MAX_VALUE
.
这种溢出情况由下一个 if 处理。当newCapacity
溢出时,这将是true
:MAX_ARRAY_SIZE
定义为Integer.MAX_VALUE - 8
并且Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0
是true
。将newCapacity
因此被正确地处理:hugeCapacity
方法返回MAX_ARRAY_SIZE
或Integer.MAX_VALUE
。
NB: this is what the // overflow-conscious code
comment in this method is saying.
注意:这就是// overflow-conscious code
这个方法中的评论所说的。
回答by Eran
I found this explanation:
我找到了这个解释:
On Tue, Mar 9, 2010 at 03:02, Kevin L. Stern wrote:
I did a quick search and it appears that Java is indeed two's complement based. Nonetheless, please allow me to point out that, in general, this type of code worries me since I fully expect that at some point someone will come along and do exactly what Dmytro suggested; that is, someone will change:
if (a - b > 0)
to
if (a > b)
and the entire ship will sink. I, personally, like to avoid obscurities such as making integer overflow an essential basis for my algorithm unless there is a good reason to do so. I would, in general, prefer to avoid overflow altogether and to make the overflow scenario more explicit:
if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) { // Do something } else { // Do something else }
It's a good point.
In
ArrayList
we cannot do this (or at least not compatibly), becauseensureCapacity
is a public API and effectively already accepts negative numbers as requests for a positive capacity that cannot be satisfied.The current API is used like this:
int newcount = count + len; ensureCapacity(newcount);
If you want to avoid overflow, you would need to change to something less natural like
ensureCapacity(count, len); int newcount = count + len;
Anyway, I'm keeping the overflow-conscious code, but adding more warning comments, and "out-lining" huge array creation so that
ArrayList
's code now looks like:/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { modCount++; // Overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // Overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
Webrev regenerated.
Martin
2010 年 3 月 9 日,星期二 03:02,Kevin L. Stern 写道:
我进行了快速搜索,看来 Java 确实是基于二进制补码的。尽管如此,请允许我指出,总的来说,这种类型的代码让我感到担忧,因为我完全希望在某个时候有人会出现并完全按照 Dmytro 的建议行事;也就是说,有人会改变:
if (a - b > 0)
到
if (a > b)
整艘船都会沉没。我个人喜欢避免晦涩难懂的事情,例如将整数溢出作为我算法的基本基础,除非有充分的理由这样做。一般来说,我更愿意完全避免溢出并使溢出场景更加明确:
if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) { // Do something } else { // Do something else }
这是一个很好的观点。
在
ArrayList
我们不能做到这一点(或至少不兼容),因为ensureCapacity
是一个公共的API,有效地接受已经为负数的不能满足一个积极的容量请求。当前的 API 是这样使用的:
int newcount = count + len; ensureCapacity(newcount);
如果你想避免溢出,你需要改变一些不太自然的东西,比如
ensureCapacity(count, len); int newcount = count + len;
无论如何,我保留了溢出意识代码,但添加了更多警告注释,并“概述”了巨大的数组创建,因此
ArrayList
现在的代码如下所示:/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { modCount++; // Overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // Overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
韦雷夫重生。
马丁
In Java 6, if you use the API as:
在 Java 6 中,如果您将 API 用作:
int newcount = count + len;
ensureCapacity(newcount);
And newCount
overflows (this becomes negative), if (minCapacity > oldCapacity)
will return false and you may mistakenly assume that the ArrayList
was increased by len
.
并且newCount
溢出(这变为负数),if (minCapacity > oldCapacity)
将返回 false,您可能会错误地认为ArrayList
增加了len
。
回答by Erick G. Hagstrom
Looking at the code:
查看代码:
int newCapacity = oldCapacity + (oldCapacity >> 1);
If oldCapacity
is quite large, this will overflow, and newCapacity
will be a negative number. A comparison like newCapacity < oldCapacity
will incorrectly evaluate true
and the ArrayList
will fail to grow.
如果oldCapacity
很大,这将溢出,并且newCapacity
将是一个负数。比较像newCapacity < oldCapacity
会错误地评估true
并且ArrayList
不会增长。
Instead, the code as written (newCapacity - minCapacity < 0
returns false) will allow the negative value of newCapacity
to be further evaluated in the next line, resulting in recalculating newCapacity
by invoking hugeCapacity
(newCapacity = hugeCapacity(minCapacity);
) to allow for the ArrayList
to grow up to MAX_ARRAY_SIZE
.
相反,所编写的代码(newCapacity - minCapacity < 0
返回 false)将允许newCapacity
在下一行中进一步评估的负值,导致newCapacity
通过调用hugeCapacity
( newCapacity = hugeCapacity(minCapacity);
)重新计算以允许ArrayList
增长到MAX_ARRAY_SIZE
。
This is what the // overflow-conscious code
comment is trying to communicate, though rather obliquely.
这就是// overflow-conscious code
评论试图传达的内容,尽管相当间接。
So, bottom line, the new comparison protects against allocating an ArrayList
larger than the predefined MAX_ARRAY_SIZE
while allowing it to grow right up to that limit if needed.
因此,最重要的是,新的比较可以防止分配ArrayList
大于预定义的值,MAX_ARRAY_SIZE
同时允许它在需要时增长到该限制。
回答by Doradus
The two forms behave exactly the same unless the expression a - b
overflows, in which case they are opposite. If a
is a large negative, and b
is a large positive, then (a < b)
is clearly true, but a - b
will overflow to become positive, so (a - b < 0)
is false.
这两种形式的行为完全相同,除非表达式a - b
溢出,在这种情况下它们是相反的。如果a
是大的负数,又b
是大的正数,那么(a < b)
显然是真的,但是a - b
会溢出变成正数,所以(a - b < 0)
是假的。
If you're familiar with x86 assembly code, consider that (a < b)
is implemented by a jge
, which branches around the body of the if statement when SF = OF. On the other hand, (a - b < 0)
will act like a jns
, which branches when SF = 0. Hence, these behave differently precisely when OF = 1.
如果您熟悉 x86 汇编代码,请考虑它(a < b)
是由 a 实现的jge
,当 SF = OF 时,它围绕 if 语句的主体分支。另一方面,(a - b < 0)
将像 a 一样jns
,在 SF = 0 时分支。因此,当 OF = 1 时,它们的行为完全不同。