web-dev-qa-db-ja.com

if(a-b <0)とif(a <b)の違い

私はJavaのArrayListソースコードを読んでいて、if文でいくつかの比較に気づきました。

Java 7では、メソッド grow(int)

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

Java 6では、growは存在しませんでした。メソッド ensureCapacity(int) ただし、

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

変更の背後にある理由は何ですか?それはパフォーマンスの問題なのでしょうか、それともスタイルなのでしょうか?

ゼロと比較する方が速いと想像できますが、マイナスかどうかを確認するためだけに完全な減算を実行することは、少しやり過ぎだと思います。また、バイトコードに関しては、1つ(ISUB)ではなく2つの命令(IFGEIF_ICMPGE)が関係します。

230
dejvuth

a < ba - b < 0は、2つの異なることを意味します。次のコードを検討してください。

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に近いので、newCapacityoldCapacity + 0.5 * oldCapacity)はオーバーフローしてInteger.MIN_VALUE(つまり負)になる可能性があります。次に、minCapacitynderflowsを減算して正の数に戻します。

このチェックにより、ifが実行されないことが保証されます。コードがif (newCapacity < minCapacity)として記述されている場合、この場合はtrueになります(newCapacityが負であるため)。newCapacityminCapacityに関係なくoldCapacityになります。

このオーバーフローの場合は、次のifによって処理されます。 newCapacityがオーバーフローした場合、これはtrueになります。MAX_ARRAY_SIZEInteger.MAX_VALUE - 8として定義され、Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0trueです。したがって、newCapacityは正しく処理されます。hugeCapacityメソッドは、MAX_ARRAY_SIZEまたはInteger.MAX_VALUEを返します。

NB:これは、このメソッドの// overflow-conscious codeコメントが言っていることです。

258
Tunaki

私は見つけました この説明

2010年3月9日火曜日03:02に、Kevin L. Sternは次のように書いています。

クイック検索を行ったところ、Javaは実際には2の補数に基づいているようです。それにもかかわらず、一般的に、このタイプのコードは、ある時点で誰かがやって来て、Dmytroが提案したことを正確に実行することを完全に期待しているので、私を心配させます。つまり、誰かが変更されます:

if (a - b > 0)

if (a > b)

船全体が沈みます。個人的には、正当な理由がない限り、整数オーバーフローをアルゴリズムの重要な基盤にするなどのあいまいさを避けたいと思っています。一般に、オーバーフローを完全に回避し、オーバーフローシナリオをより明確にすることを好みます。

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

それは良い点です。

ArrayListはパブリックAPIであり、満足できない正のキャパシティの要求として負の数をすでに事実上受け入れているため、ensureCapacityではこれを実行できません(少なくとも互換性がありません)。

現在の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が再生成されました。

マーティン

Java 6で、APIを次のように使用する場合:

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

また、newCountがオーバーフローすると(これは負になります)、if (minCapacity > oldCapacity)はfalseを返し、ArrayListlenだけ増加したと誤って想定する可能性があります。

92
Eran

コードを見る:

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

oldCapacityが非常に大きい場合、これはオーバーフローし、newCapacityは負の数になります。 newCapacity < oldCapacityのような比較はtrueを誤って評価し、ArrayListは大きくなりません。

代わりに、記述されたコード(newCapacity - minCapacity < 0はfalseを返します)は、次の行でnewCapacityの負の値をさらに評価し、newCapacityを呼び出してhugeCapacityを再計算します。 (newCapacity = hugeCapacity(minCapacity);ArrayListMAX_ARRAY_SIZEに成長できるようにします。

これは、// overflow-conscious codeコメントが通信しようとしているものですが、かなり斜めになっています。

結論として、新しい比較は、定義済みのMAX_ARRAY_SIZEよりも大きいArrayListを割り当てることを防ぎ、必要に応じてその限界まで拡張できるようにします。

16

a - bがオーバーフローしない限り、2つの形式はまったく同じように動作します。 aが大きな負で、bが大きな正の場合、(a < b)は明らかに真ですが、a - bはオーバーフローして正になるため、(a - b < 0)偽です。

X86アセンブリコードに精通している場合は、(a < b)jgeによって実装されていることを考慮してください。一方、(a - b < 0)は、SF = 0の場合に分岐するjnsのように動作します。したがって、OF = 1の場合、これらは異なる動作をします。

0
Doradus