İf (a - b <0) ile if (a <b) arasındaki fark


252

Java'nın ArrayListkaynak kodunu okuyordum ve if-ifadelerinde bazı karşılaştırmalar fark ettim.

Java 7, yöntem grow(int)kullanır

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

Java 6'da growyoktu. Yöntem ensureCapacity(int), ancak kullanır

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

Değişimin arkasındaki sebep neydi? Bir performans sorunu muydu yoksa sadece bir tarz mıydı?

Sıfıra karşı karşılaştırmanın daha hızlı olduğunu hayal edebiliyorum, ancak negatif olup olmadığını kontrol etmek için tam bir çıkarma yapmak bana biraz abartılı geliyor. Ayrıca bayt kodu açısından, bu bir ( ) yerine iki talimat ( ISUBve IF_ICMPGE) içerecektir IFGE.


35
@Tunaki Taşmayı önleme açısından nasıl daha if (newCapacity - minCapacity < 0)iyi if (newCapacity < minCapacity)?
Eran

3
Bahsedilen işaret taşmasının gerçekten sebebi olup olmadığını merak ediyorum. Çıkarma, taşma için daha çok aday gibi görünüyor. Bileşen belki "bu yine de taşmayacaktır" diyebilir, belki her iki değişken de negatif değildir.
Joop Eggen

12
Bilginize, bir karşılaştırma yapmanın "tam bir çıkarma" yapmaktan daha hızlı olduğuna inanıyorsunuz. Deneyimlerime göre, makine kodu düzeyinde, genellikle bir çıkarma, sonuç atma ve ortaya çıkan bayrakları kontrol ederek karşılaştırmalar yapılır.
David Dubois

6
@David Dubois: OP, karşılaştırmanın çıkarma işleminden daha hızlı olduğunu varsaymadı, ancak sıfır ile karşılaştırma, iki rastgele değerin karşılaştırılmasından daha hızlı olabilir ve ayrıca önce gerçek bir çıkarma işlemi yaparken bunun geçerli olmadığını varsayar. sıfır ile karşılaştırılacak bir değer elde etmek için. Hepsi oldukça makul.
Holger

Yanıtlar:


285

a < bve a - b < 0iki farklı anlama gelebilir. Aşağıdaki kodu göz önünde bulundurun:

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

Çalıştırıldığında, bu yalnızca yazdırılır a - b < 0. Olan şey a < baçıkça yanlıştır, ancak a - btaşar ve olur -1, bu da negatiftir.

Şimdi, bunu söyledikten sonra, dizinin gerçekten yakın bir uzunluğa sahip olduğunu düşünün Integer.MAX_VALUE. İçindeki kod ArrayListşöyle:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);

oldCapacityyakın gerçekten Integer.MAX_VALUEöylesine newCapacity(ki oldCapacity + 0.5 * oldCapacity) taşması ve hale gelebilir Integer.MIN_VALUE(yani negatif). Daha sonra, minCapacity alt-akışları tekrar pozitif bir sayıya çıkarmak .

Bu kontrol if, çalıştırılmamasını sağlar. Kod olarak yazıldıysa if (newCapacity < minCapacity), truebu durumda ( newCapacitynegatif olduğu için) newCapacityolurdu , bu yüzden minCapacityne olursa olsun oldCapacity.

Bu taşma durumu sonraki if tarafından işlenir. Ne zaman newCapacitytaştı, bu olacak true: MAX_ARRAY_SIZEolarak tanımlanır Integer.MAX_VALUE - 8ve Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0bir true. Bu newCapacitynedenle, doğru şekilde işlenir: hugeCapacityyöntem MAX_ARRAY_SIZEveya döndürür Integer.MAX_VALUE.

Not: Bu // overflow-conscious codeyöntemdeki yorum budur.


8
Matematik ve CS arasındaki fark üzerine iyi bir demo
piggybox

36
@piggybox öyle demezdim. Bu matematik. Sadece Z'de matematik değil, modulo 2 ^ 32 tamsayılarının bir versiyonunda (kanonik gösterimler normalden farklı seçilmiştir). Sadece "lol bilgisayarlar ve tuhaflıkları" değil, doğru bir matematik sistemi.
harold

2
Hiç taşmayan bir kod yazardım.
Aleksandr Dubinsky

IIRC işlemcileri a - b, üst bitin a olup olmadığını kontrol ederek işaretli tamsayılara göre daha az bir talimat uygular 1. Taşmayı nasıl ele alırlar?
Ben Leggiero

2
@ BenC.R. Laggiero x86, diğerlerinin yanı sıra, koşullu talimatlarla kullanılmak üzere durum koşullarını kullanarak farklı koşulları ayrı bir kayıtta takip eder. Bu yazmaç, sonucun işareti, sonucun sıfırlığı ve son aritmetik işlemde aşırı / düşük akış olup olmadığı için ayrı bitlere sahiptir.

105

Bu açıklamayı buldum :

9 Mart 2010 Salı, 03:02, Kevin L. Stern şunu yazdı:

Hızlı bir arama yaptım ve Java gerçekten iki tamamlayıcı tabanlı görünüyor. Yine de, genel olarak, bu tür bir kodun beni endişelendirdiğini belirtmeme izin verin, çünkü bir noktada birisinin gelip tam olarak Dmytro'nun önerdiğini yapmasını bekliyorum; yani, birisi değişecek:

if (a - b > 0)

için

if (a > b)

ve tüm gemi batacak. Şahsen, bunun için iyi bir neden olmadığı sürece, tamsayı taşmasını algoritmam için temel bir temel haline getirmek gibi belirsizlerden kaçınmak istiyorum. Genel olarak, taşmayı tamamen önlemeyi ve taşma senaryosunu daha açık hale getirmeyi tercih ederim:

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

İyi bir nokta.

In ArrayListçünkü biz, (ya da en azından değil uyuşan) bunu yapamaz ensureCapacitybir genel API ve etkin bir şekilde zaten memnun olamaz olumlu kapasite için istekleri gibi negatif sayılar kabul eder.

Mevcut API şu şekilde kullanılır:

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

Taşmayı önlemek istiyorsanız, daha doğal bir şeye geçmeniz gerekir.

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

Her neyse, taşma bilinçli kod tutuyorum, ancak daha fazla uyarı yorumları ekleyerek ve " ArrayListkod dizisi şimdi" gibi görünüyor böylece büyük dizi oluşturma "out-astar" :

/**
 * 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 yeniden oluşturuldu.

kırlangıç

Java 6'da API'yı şu şekilde kullanırsanız:

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

Ve newCounttaşmalar (bu negatif olur), if (minCapacity > oldCapacity)yanlış döndürür ve yanlışlıkla bunun ArrayListarttığını varsayabilirsiniz len.


2
Güzel bir fikir ama uygulanmasıylaensureCapacity çelişiyor ; eğer minCapacitynegatifse, o noktaya asla ulaşamazsınız - karmaşık uygulamanın engelliyormuş gibi sessizce göz ardı edilir. Dolayısıyla, genel API uyumluluğu için “bunu yapamayız” zaten olduğu gibi garip bir argüman. Bu davranışa güvenen yalnızca arayanlar dahili olanlardır.
Holger

1
@Holger minCapacityÇok negatifse (yani int, ArrayList'in geçerli boyutunu eklemek istediğiniz öğe sayısına eklenirken taşmadan kaynaklanır ), minCapacity - elementData.lengthtekrar taşmak ve pozitif olmak için. Ben böyle anlıyorum.
Eran

1
@Holger Ancak, Java 8'de tekrar değiştirdiler, if (minCapacity > minExpand)anlamadım.
Eran

Evet, mevcut addAllyöntemin toplamı ve yeni elemanların sayısı taşabileceğinden , iki yöntem alakalı olduğu tek durumdur. Bununla birlikte, bunlar dahili görüşmelerdir ve “bunu ensureCapacityortak bir API olduğu için değiştiremeyiz ” argümanı, aslında, ensureCapacitynegatif değerleri görmezden geldiğinde garip bir argüman . Java 8 API'sı bu davranışı değiştirmedi, tek yaptığı şey ArrayListbaşlangıç ​​durumundayken varsayılan kapasitenin altındaki kapasiteleri yok saymaktır (yani varsayılan kapasite ile başlatıldı ve hala boş).
Holger

Başka bir deyişle, newcount = count + lendahili kullanım söz konusu olduğunda mantık doğrudur, ancak bu publicyöntem için geçerli değildir ensureCapacity()
Holger

19

Koda bakarak:

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

Eğer oldCapacityoldukça büyük, bu taşar ve newCapacitynegatif bir sayı olacaktır. Gibi bir karşılaştırma newCapacity < oldCapacityyanlış değerlendirir trueve ArrayListbüyüyemez.

Bunun yerine, yazılan kod ( newCapacity - minCapacity < 0false döndürür) newCapacitysonraki satırda negatif değerinin daha fazla değerlendirilmesine izin vererek , büyümesine izin vermek için newCapacityinvoke hugeCapacity( newCapacity = hugeCapacity(minCapacity);) yöntemiyle yeniden hesaplama sağlar .ArrayListMAX_ARRAY_SIZE

Bu, // overflow-conscious codeyorumun oldukça eğik olsa da iletişim kurmaya çalıştığı şeydir .

Sonuç olarak, yeni karşılaştırma ArrayList, önceden tanımlanmış olandan daha büyük bir tahsis edilmesine karşı koruma MAX_ARRAY_SIZEsağlarken, gerektiğinde bu sınıra kadar büyümesine izin verir.


1

İki form, ifade a - btaşmadığı sürece tam olarak aynı davranır , bu durumda zıttırlar. Eğer abüyük bir negatif olduğunu ve bbüyük bir pozitif olduğu, daha sonra (a < b)açıkça doğrudur, fakat a - bbu yüzden, pozitif olmaya taşar (a - b < 0)yanlıştır.

X86 derleme kodunu biliyorsanız, SF = OF olduğunda if ifadesinin gövdesi etrafında dallanan (a < b)bir a tarafından uygulandığını düşünün jge. Öte yandan, SF = 0 olduğunda dallanan (a - b < 0)a gibi jnsdavranacaktır. Bu nedenle, OF = 1 olduğunda bunlar farklı şekilde davranır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.