it-swarm.com.de

Unterschied zwischen if (a - b <0) und if (a <b)

Ich las Javas ArrayList-Quellcode und bemerkte einige Vergleiche in if-Anweisungen.

In Java 7 wird die Methode grow(int) verwendet

if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

In Java 6 war grow nicht vorhanden. Die Methode ensureCapacity(int) verwendet jedoch

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

Was war der Grund für die Veränderung? War es ein Leistungsproblem oder nur ein Stil? 

Ich könnte mir vorstellen, dass der Vergleich mit Null schneller ist, aber eine vollständige Subtraktion, nur um zu prüfen, ob sie negativ ist, erscheint mir etwas übertrieben. Auch in Bezug auf Bytecode würde dies zwei Anweisungen (ISUB und IF_ICMPGE) anstelle von einer (IFGE) beinhalten.

229
dejvuth

a < b und a - b < 0 können zwei verschiedene Bedeutungen haben. Betrachten Sie den folgenden Code:

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");
}

Bei Ausführung wird nur a - b < 0 gedruckt. Was passiert ist, dass a < b eindeutig falsch ist, aber a - b überläuft und -1 wird, was negativ ist.

Bedenken Sie jedoch, dass das Array eine Länge hat, die wirklich nahe an Integer.MAX_VALUE liegt. Der Code in ArrayList sieht folgendermaßen aus:

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 ist wirklich in der Nähe von Integer.MAX_VALUE, daher könnte newCapacity (was oldCapacity + 0.5 * oldCapacity ist) überlaufen und Integer.MIN_VALUE (d. h. negativ) werden. Subtrahieren Sie dann minCapacity underflows zurück in eine positive Zahl.

Diese Prüfung stellt sicher, dass die if nicht ausgeführt wird. Wenn der Code als if (newCapacity < minCapacity) geschrieben würde, wäre dies in diesem Fall true (da newCapacity negativ ist), sodass die newCapacity unabhängig von minCapacity zur oldCapacity gezwungen wird.

Dieser Überlauffall wird vom nächsten if behandelt. Wenn newCapacity übergelaufen ist, ist dies true: MAX_ARRAY_SIZE ist definiert als Integer.MAX_VALUE - 8 und Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0 ist true. Die Variable newCapacity wird daher richtig behandelt: Die Methode hugeCapacity gibt MAX_ARRAY_SIZE oder Integer.MAX_VALUE zurück.

NB: Das sagt der // overflow-conscious code-Kommentar in dieser Methode.

257
Tunaki

Ich habe diese Erklärung gefunden :

Am Dienstag, 9. März 2010 um 03:02 Uhr schrieb Kevin L. Stern: 

Ich habe eine schnelle Suche durchgeführt und es scheint, dass Java in der Tat das Zweierpack ist basierend. Erlauben Sie mir trotzdem, darauf hinzuweisen, dass diese Die Art des Codes macht mir Sorgen, da ich völlig damit rechne, dass irgendwann jemand Kommen Sie mit und machen Sie genau das, was Dmytro vorgeschlagen hat. das heißt, jemand wird Veränderung:

if (a - b > 0)

zu

if (a > b)

und das ganze Schiff wird sinken. Ich persönlich mag es, Unklarheiten zu vermeiden B. den Integer-Überlauf zu einer wesentlichen Grundlage für meinen Algorithmus machen, sofern nicht Dafür gibt es einen guten Grund. Im Allgemeinen würde ich es vorziehen, zu vermeiden Überlauf insgesamt und um das Überlaufszenario deutlicher zu machen:

if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) {
   // Do something
} else {
  // Do something else
}

Das ist ein guter Punkt.

In ArrayList können wir das nicht (oder zumindest nicht kompatibel), weil ensureCapacity ist eine öffentliche API und akzeptiert bereits negative Zahlen als Anfragen nach einer positiven Kapazität, die nicht sein kann zufrieden.

Die aktuelle API wird wie folgt verwendet:

int newcount = count + len;
ensureCapacity(newcount);

Wenn Sie einen Überlauf vermeiden möchten, müssen Sie etwas ändern weniger natürlich

ensureCapacity(count, len);
int newcount = count + len;

Wie auch immer, ich halte den überlaufbewussten Code, füge aber mehr .__ hinzu. Warnungskommentare und "Outlining" der Erstellung großer Arrays, so dass Der Code von ArrayList sieht jetzt so aus:

/**
 * 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 neu erstellt.

Martin

Wenn Sie in Java 6 die API wie folgt verwenden:

int newcount = count + len;
ensureCapacity(newcount);

Und newCount läuft über (dies wird negativ), if (minCapacity > oldCapacity) wird false zurückgeben und Sie können irrtümlicherweise annehmen, dass ArrayList um len erhöht wurde.

92
Eran

Blick auf den Code:

int newCapacity = oldCapacity + (oldCapacity >> 1);

Wenn oldCapacity ziemlich groß ist, wird dies überlaufen und newCapacity ist eine negative Zahl. Ein Vergleich wie newCapacity < oldCapacity wertet true falsch aus und die ArrayList wächst nicht.

Stattdessen ermöglicht der Code, wie geschrieben (newCapacity - minCapacity < 0 gibt false), den negativen Wert von newCapacity in der nächsten Zeile weiter auszuwerten, was zur Neuberechnung von newCapacity durch Aufrufen von hugeCapacity (newCapacity = hugeCapacity(minCapacity);) führt, damit die ArrayList auf MAX_ARRAY_SIZE aufwachsen kann.

Dies ist es, was der // overflow-conscious code-Kommentar versucht, zu kommunizieren, wenn auch etwas schräg.

Unterm Strich schützt der neue Vergleich also vor der Zuweisung einer ArrayList, die größer als der vordefinierte MAX_ARRAY_SIZE ist, und lässt ihn bei Bedarf bis zu diesem Grenzwert anwachsen.

16

Die beiden Formulare verhalten sich genau gleich, wenn der Ausdruck a - b nicht überläuft. In diesem Fall liegen sie entgegengesetzt. Wenn a ein großes Negativ ist und b ein großes Positiv ist, ist (a < b) eindeutig wahr, aber a - b wird überlaufen und wird positiv, so dass (a - b < 0) falsch ist.

Wenn Sie mit x86-Assemblycode vertraut sind, sollten Sie bedenken, dass (a < b) von einer jge implementiert wird, die sich bei SF = OF um den Rumpf der if-Anweisung verzweigt. Auf der anderen Seite verhält sich (a - b < 0) wie eine jns, die verzweigt, wenn SF = 0 ist. Daher verhalten sich diese genau, wenn OF = 1 ist.

0
Doradus