Разница между if (a - b <0) и if (a <b)
Я читал исходный код Java ArrayList
и замечал некоторые сравнения в if-statement.
В Java 7 метод grow(int)
использует
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
В Java 6, grow
не существовало. Однако метод ensureCapacity(int)
использует
if (newCapacity < minCapacity)
newCapacity = minCapacity;
В чем причина изменения? Это проблема производительности или просто стиль?
Я мог представить себе, что сравнение с нолем происходит быстрее, но выполнение полного вычитания просто для проверки того, кажется ли это негативным для меня немного излишним. Также в терминах байт-кода это будет включать две команды (ISUB
и IF_ICMPGE
) вместо одного (IFGE
).
Ответы
Ответ 1
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");
}
При запуске будет напечатано только a - b < 0
. Случается, что a < b
явно ложно, но a - b
переполняется и становится -1
, что отрицательно.
Теперь, сказав это, учтите, что массив имеет длину, которая действительно близка к 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
действительно близок к Integer.MAX_VALUE
, поэтому newCapacity
(который oldCapacity + 0.5 * oldCapacity
) может переполняться и стать Integer.MIN_VALUE
(т.е. отрицательным). Затем вычитание minCapacity
возвращается обратно в положительное число.
Эта проверка гарантирует, что if
не будет выполнен. Если код был записан как if (newCapacity < minCapacity)
, в этом случае он был бы true
(так как newCapacity
отрицательный), поэтому newCapacity
будет принудительно minCapacity
независимо от oldCapacity
.
Этот случай переполнения обрабатывается следующим if. Когда newCapacity
переполнится, это будет true
: MAX_ARRAY_SIZE
определяется как Integer.MAX_VALUE - 8
и Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0
is true
. Поэтому newCapacity
корректно обрабатывается: метод hugeCapacity
возвращает MAX_ARRAY_SIZE
или Integer.MAX_VALUE
.
NB: это комментарий // overflow-conscious code
в этом методе.
Ответ 2
Я нашел это объяснение:
Вторник, 9 мар 2010 в 03:02, Кевин Л. Стерн писал (а):
Я сделал быстрый поиск, и кажется, что Java действительно два дополнения исходя из. Тем не менее, позвольте мне указать, что в целом это тип кода беспокоит меня, так как я полностью ожидаю, что в какой-то момент кто-то будет придите и сделайте то, что предложил Дмитрий. то есть кто-то будет изменить:
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;
}
Обновлен Webrev.
Martin
В Java 6, если вы используете API как:
int newcount = count + len;
ensureCapacity(newcount);
И newCount
переполнения (это становится отрицательным), if (minCapacity > oldCapacity)
вернет false, и вы можете ошибочно предположить, что значение ArrayList
было увеличено на len
.
Ответ 3
Взглянув на код:
int newCapacity = oldCapacity + (oldCapacity >> 1);
Если oldCapacity
достаточно велико, это будет переполняться, а newCapacity
будет отрицательным числом. Сравнение типа newCapacity < oldCapacity
будет неправильно оценивать true
, а ArrayList
не будет расти.
Вместо этого код, записанный (newCapacity - minCapacity < 0
возвращает false), позволит продолжить оценку отрицательного значения newCapacity
в следующей строке, что приведет к пересчету newCapacity
путем вызова hugeCapacity
(newCapacity = hugeCapacity(minCapacity);
) чтобы ArrayList
вырасти до MAX_ARRAY_SIZE
.
Это то, что комментарий // overflow-conscious code
пытается связываться, хотя и наклонно.
Итак, в нижней строке новое сравнение защищает от выделения ArrayList
больше, чем предопределенный MAX_ARRAY_SIZE
, позволяя ему расти вплоть до этого предела, если это необходимо.
Ответ 4
Две формы ведут себя точно так же, если выражение a - b
не переполняется, и в этом случае они противоположны. Если a
является большим отрицательным, а b
является большим положительным, то (a < b)
явно истинно, но a - b
будет переполняться, чтобы стать положительным, поэтому (a - b < 0)
является ложным.
Если вы знакомы с ассемблером x86, считайте, что (a < b)
реализуется jge
, который веткится вокруг тела оператора if, когда SF = OF. С другой стороны, (a - b < 0)
будет действовать как a jns
, который веткится, когда SF = 0. Следовательно, они ведут себя по-разному точно, когда OF = 1.