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


252

私はJavaのArrayListソースコードを読んでいて、ifステートメントの比較に気づきました。

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

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

Java 6ではgrow存在しませんでした。ensureCapacity(int)ただし、この方法では

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

変更の理由は何ですか?パフォーマンスの問題ですか、それとも単なるスタイルですか?

ゼロと比較する方が速いと想像できますが、それが負かどうかを確認するためだけに完全な減算を実行することは、私には少しやり過ぎに思えます。また、バイトコードに関しては、これは1つ()ではなく2つの命令(ISUBおよびIF_ICMPGE)を伴いIFGEます。


35
@Tunaki オーバーフローを防ぐという点で、どのようにif (newCapacity - minCapacity < 0)優れていif (newCapacity < minCapacity)ますか?
Eran

3
言及された記号のオーバーフローが本当に理由であるかどうか疑問に思います。減算はオーバーフローの候補のようです。コンポーネントは「それでもオーバーフローしない」と言っているかもしれません。おそらく両方の変数は負ではありません。
Joop Eggen、2015年

12
ちなみに、比較を行う方が「完全な減算」を行うよりも速いとあなたは信じています。私の経験では、マシンコードレベルでは、通常、比較は減算を実行し、結果を破棄し、結果のフラグをチェックすることによって行われます。
David Dubois

6
@David Dubois:OPは比較が減算よりも高速であるとは想定していませんでしたが、ゼロとの比較は2つの任意の値の比較よりも高速である可能性があり、実際に減算を最初に実行している場合はこれが成立しないと正しく想定していますゼロと比較する値を取得するため。それはすべてかなり合理的です。
Holger

回答:


285

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負なので)であるため、newCapacityminCapacity関係なくが強制されoldCapacityます。

このオーバーフローのケースは、次のifによって処理されます。ときにnewCapacityオーバーフローした、これは次のようになりますtrueMAX_ARRAY_SIZEとして定義されているInteger.MAX_VALUE - 8Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0ありますtruenewCapacity従って正しく処理される:hugeCapacityメソッドの戻りMAX_ARRAY_SIZEまたはInteger.MAX_VALUE

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


8
数学とCSの違いに関する優れたデモ
piggybox '16年

36
@piggybox私はそうは思いません。これは数学です。これは単にZの数学ではなく、2 ^ 32を法とする整数のバージョンです(正規表現は通常とは異なる方法で選択されています)。これは、「コンピューターとその癖」だけでなく、適切な数学的システムです。
ハロルド

2
まったくオーバーフローしないコードを書いたでしょう。
Aleksandr Dubinsky、

IIRCプロセッサa - bは、最上位ビットがであるかどうかを確認することで、符号付き整数に小なり命令を実装します1。彼らはどのようにオーバーフローを処理しますか?
Ben Leggiero、2015年

2
@ BenC.R.Leggiero x86などは、条件付き命令で使用するために、別個のレジスターのステータスフラグを通じてさまざまな条件を追跡します。このレジスタには、結果の符号、結果のゼロ、および最後の算術演算でオーバーフロー/アンダーフローが発生したかどうかについて、個別のビットがあります。

105

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

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を行うことはできません(または少なくとも互換性がありません)。これ 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が再生成されました。

マーティン

Java 6では、APIを次のように使用した場合:

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

そして、newCountオーバーフロー(これは負にif (minCapacity > oldCapacity)なります)はfalseを返し、ArrayListがによって増加したと誤って想定するかもしれませんlen


2
素晴らしいアイデアですが、の実装ensureCapacityと矛盾しています。minCapacityが負の場合、そのポイントに到達することはありません。複雑な実装が防止するふりをしているのと同じように、黙って無視されます。したがって、パブリックAPIの互換性のために「これを行うことはできません」とは、彼らがすでに行ったように奇妙な議論です。この動作に依存する唯一の呼び出し元は内部の呼び出し元です。
Holger

1
@Holger If minCapacityが非常に負の場合(つまりint、追加したい要素の数にArrayListの現在のサイズを追加したときのオーバーフローの結果)、 minCapacity - elementData.length再びオーバーフローして正になります。それが私の理解です。
Eran

1
@Holgerただし、Java 8で再度変更しましたがif (minCapacity > minExpand)、理解できません。
Eran

はい、addAll現在のサイズと新しい要素の数の合計がオーバーフローする可能性があるため、2つの方法が関連する唯一のケースです。それでも、これらは内部呼び出しであり、「ensureCapacityパブリックAPI であるため変更できません」という引数は、実際にensureCapacityは負の値を無視するのに奇妙な引数です。Java 8 APIはその動作を変更しませんでした。ArrayList初期状態(つまり、デフォルトの容量で初期化され、まだ空)の場合、デフォルトの容量を下回る容量は無視されます。
Holger

言い換えれば、newcount = count + len内部使用に関しては、その理由付けは正しいですが、publicメソッドには適用されませんensureCapacity()
Holger

19

コードを見る:

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

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

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

これは // overflow-conscious codeコメントが伝えようとしていることですが、斜めではありません。

つまり、新しい比較ではArrayList、事前定義された値よりも大きい値を割り当てないように保護しMAX_ARRAY_SIZEながら、必要に応じてその上限まで拡張できるようにします。


1

2つの形式は、式a - bがオーバーフローしない限りまったく同じように動作します。場合はa大きな負で、かつb大きな正で、その後、(a < b)明らかに事実であるが、a - bそう、ポジティブになるためにオーバーフローします(a - b < 0)falseです。

x86アセンブリコードに精通している場合(a < b)jge、それがによって実装されていると考えてください。これは、SF = OFのときにifステートメントの本体の周りを分岐します。一方、(a - b < 0)jns、SF = 0の場合に分岐するのように動作します。したがって、OF = 1の場合、これらは正確に異なる動作をします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.