Java'da iki sayıyı çarpmanın taşmaya neden olup olmayacağını nasıl kontrol edebilirim?


101

İki sayıyı çarpmanın taşmaya neden olduğu özel durumu ele almak istiyorum. Kod şuna benzer:

int a = 20;
long b = 30;

// if a or b are big enough, this result will silently overflow
long c = a * b;

Bu basitleştirilmiş bir versiyon. Gerçek programda ave bçalışma zamanında başka bir yerden kaynaklanır. Başarmak istediğim şey şuna benzer:

long c;
if (a * b will overflow) {
    c = Long.MAX_VALUE;
} else {
    c = a * b;
}

Bunu en iyi nasıl kodlamamı önerirsin?

Güncelleme: ave bsenaryomda her zaman olumsuz değildir.


6
Java'nın, C # 'da yapıldığı gibi CPU'nun taşma bayrağına dolaylı erişim sağlamaması çok kötü .
Drew Noakes

Yanıtlar:


92

Java 8 sahiptir Math.multiplyExact, Math.addExactints ve uzun vb. Bunlar ArithmeticExceptiontaşma üzerine kontrolsüz bir şekilde atarlar .


59

Eğer ave bikisi de pozitif o zaman kullanabilirsiniz:

if (a != 0 && b > Long.MAX_VALUE / a) {
    // Overflow
}

Hem pozitif hem de negatif sayılarla uğraşmanız gerekiyorsa, o zaman daha karmaşıktır:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if (a != 0 && (b > 0 && b > maximum / a ||
               b < 0 && b < maximum / a))
{
    // Overflow
}

İşte bunu kontrol etmek için kırbaçladığım küçük bir tablo, taşmanın -10 veya +10'da gerçekleşiyormuş gibi davranarak:

a =  5   b =  2     2 >  10 /  5
a =  2   b =  5     5 >  10 /  2
a = -5   b =  2     2 > -10 / -5
a = -2   b =  5     5 > -10 / -2
a =  5   b = -2    -2 < -10 /  5
a =  2   b = -5    -5 < -10 /  2
a = -5   b = -2    -2 <  10 / -5
a = -2   b = -5    -5 <  10 / -2

Senaryomda a ve b'nin her zaman negatif olmadığını belirtmeliyim ki bu da bu yaklaşımı biraz basitleştirecektir.
Steve McLeod

3
Bunun bir durumda başarısız olabileceğini düşünüyorum: a = -1 ve b = 10. Maksimum / a ifadesi, Tamsayı.MIN_VALUE ile sonuçlanır ve hiçbiri olmadığında taşma algılar
Kyle,

Bu gerçekten güzel. Merak edenler için, bu işleri nedeni tamsayı için olmasıdır n, n > xaynıdır n > floor(x). Pozitif tamsayılar için bölme örtük bir taban yapar. (Negatif sayılar için onun yerine
yuvarlanır

a = -1Ve b = 10sorunu ele almak için aşağıdaki cevabıma bakın.
Jim Pivarski

17

Uzun taşma / yetersizliği kontrol eden güvenli aritmetik işlemler sağlayan Java kitaplıkları vardır. Örneğin, Guava en LongMath.checkedMultiply (uzun uzun, b) ürününü verir ave bo değil taşması ve atar Sağlanan, ArithmeticExceptioneğer a * btaşmaları imzalanan longaritmetik.


4
Bu en iyi cevap - Java'da makine aritmetiğini gerçekten anlayan kişiler tarafından uygulanan ve birçok kişi tarafından test edilmiş bir kitaplık kullanın. Kendi kodunuzu yazmaya çalışmayın veya diğer cevaplarda yayınlanan yarı pişmiş denenmemiş kodlardan herhangi birini kullanmayın!
Zengin

@Enerccio - Yorumunuzu anlamıyorum. Guava'nın tüm sistemlerde çalışmayacağını mı söylüyorsunuz? Java'nın yaptığı her yerde çalışacağına dair sizi temin ederim. Kodu yeniden kullanmanın genel olarak kötü bir fikir olduğunu mu söylüyorsunuz? Eğer öyleyse katılmıyorum.
Zengin

2
@Rich Diyorum ki, bir işlevi kullanabilmeniz için büyük bir kütüphane eklemek kötü bir fikir.
Enerccio

Neden? Örneğin bir işletme için büyük bir uygulama yazıyorsanız, sınıf yolunda fazladan bir JAR zarar vermez ve Guava'nın içinde çok sayıda çok yararlı kod vardır. Özenle test edilmiş kodlarını yeniden kullanmak, kendi versiyonunuzu yazmaya çalışmaktan çok daha iyidir (ki önerdiğiniz şey olduğunu varsayıyorum?). Fazladan bir JAR'ın çok pahalı olacağı bir ortamda yazıyorsanız (nereye? Gömülü Java?), O zaman belki de Guava'dan yalnızca bu sınıfı çıkarmalısınız. StackOverflow'dan test edilmemiş bir cevabı kopyalamak, Guava'nın dikkatlice test edilmiş kodunu kopyalamaktan daha mı iyidir?
Zengin

Koşullu eğer-o zaman başa çıkabilecek bir şey için bir istisna atmak biraz fazla değil mi?
victtim

6

Bunun yerine java.math.BigInteger kullanabilir ve sonucun boyutunu kontrol edebilirsiniz (kodu test etmediniz):

BigInteger bigC = BigInteger.valueOf(a) * multiply(BigInteger.valueOf(b));
if(bigC.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
  c = Long.MAX_VALUE;
} else {
  c = bigC.longValue()
}

7
Bu çözümü oldukça yavaş buluyorum
nothrow

Muhtemelen bunu yapmanın en iyi yolu budur. Bunun sayısal bir uygulama olduğunu varsaydım, bu yüzden onu önceden önermedim, ama bu muhtemelen bu sorunu çözmenin en iyi yolu.
Stefan Kendall

2
BigInteger ile '>' operatörünü kullanabileceğinizden emin değilim. CompareTo yöntemi kullanılmalıdır.
Pierre

CompareTo olarak değiştirildi ve hız önemli olabilir veya olmayabilir, kodun kullanılacağı koşullara bağlıdır.
Ulf Lindback

5

Sonucun boyutunu kontrol etmek için logaritma kullanın.


Şunu yapın: ceil(log(a)) + ceil(log(b)) > log(Long.MAX)?
Thomas Jung

1
Ben kontrol ettim. Küçük değerler için BigInteger'dan% 20 daha hızlıdır ve MAX'a yakın değerler için neredeyse aynıdır (% 5 daha hızlı). Yossarian'ın kodu en hızlısıdır (BigInteger'dan% 95 ve% 75 daha hızlı).
Thomas Jung

Bazı durumlarda başarısız olabileceğinden şüpheleniyorum.
Tom Hawtin - tackline

Bir tamsayı günlüğünün etkili bir şekilde sadece baştaki sıfırların sayısını saydığını ve bazı yaygın durumları optimize edebileceğinizi unutmayın (örneğin, ((a | b) & 0xffffffff00000000L) == 0) güvende olduğunuzu biliyorsanız). Öte yandan, en yaygın durumlar için optimizasyonunuzu 30/40-ish saat döngülerine indiremezseniz, John Kugelman'ın yöntemi muhtemelen daha iyi performans gösterecektir (hatırladığım kadarıyla bir tamsayı bölümü c. 2 bit / saat döngüsüdür).
Neil Coffey

PS Üzgünüm, AND maskesinde fazladan bir bit setine ihtiyacım olduğunu düşünüyorum (0xffffffff80000000L) - biraz geç, ama anladınız ...
Neil Coffey

4

Java'da int.MaxValue gibi bir şey var mı? Eğer evet ise, dene

if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
 // it will overflow
}

düzenleme: söz konusu Long.MAX_VALUE görüntülendi


Ben downvote değil ama vermedi Math.Abs(a)eğer işi yapmaz aise Long.MIN_VALUE.
John Kugelman

@John - a ve b> 0. Yossarian'ın yaklaşımının (b! = 0 && a> Long.MAX_VALUE / b) en iyisi olduğunu düşünüyorum.
Thomas Jung

@Thomas, a ve b> = 0, yani negatif değil.
Steve McLeod

2
Doğru, ama bu durumda Abs'e gerek yok. Negatif sayılara izin verilirse, bu en az bir uç durum için başarısız olur. Tüm söylediğim bu, sadece titiz davranıyorum.
John Kugelman

Java'da Math.Abs ​​değil Math.abs kullanmalısınız (C # guy?)
dfa

4

İşte aklıma gelen en basit yol

int a = 20;
long b = 30;
long c = a * b;

if(c / b == a) {
   // Everything fine.....no overflow
} else {
   // Overflow case, because in case of overflow "c/b" can't equal "a"
}

3

Jruby'den çalındı

    long result = a * b;
    if (a != 0 && result / a != b) {
       // overflow
    }

GÜNCELLEME: Bu kod kısa ve iyi çalışıyor; ancak, a = -1, b = Long.MIN_VALUE için başarısız olur.

Olası bir geliştirme:

long result = a * b;
if( (Math.signum(a) * Math.signum(b) != Math.signum(result)) || 
    (a != 0L && result / a != b)) {
    // overflow
}

Bunun herhangi bir bölünme olmaksızın bazı taşmaları yakalayacağını unutmayın.


Math.signum yerine Long.signum kullanabilirsiniz
aditsu SE EVIL olduğundan çıkılır.

3

Daha önce belirtildiği gibi, Java 8, taşma durumunda istisnalar atan Math.xxxExact yöntemlerine sahiptir.

Projeniz için Java 8 kullanmıyorsanız, oldukça kompakt olan uygulamalarını yine de "ödünç" alabilirsiniz.

JDK kaynak kodu havuzundaki bu uygulamalara bazı bağlantılar aşağıda verilmiştir, bunların geçerli olup olmayacağına dair bir garanti yoktur, ancak her durumda JDK kaynağını indirebilmeli ve java.lang.Mathsınıf içinde sihirlerini nasıl yaptıklarını görmelisiniz .

Math.multiplyExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l925

Math.addExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l830

vs vs.

GÜNCELLENDİ: Açık JDK'nın Mercurial depolarına bağlantılara yönelik üçüncü taraf web sitesine geçersiz bağlantılar değiştirildi.


2

Neden kimsenin çözüme bakmadığından emin değilim:

if (Long.MAX_VALUE/a > b) {
     // overflows
} 

İki sayıdan daha büyük olması için a seçin.


2
aBüyük veya küçük olmasının önemli olduğunu sanmıyorum.
Thomas Ahle

2

Doğrudan düzenleyerek değiştirmeden John Kugelman'ın cevabını geliştirmek istiyorum. Simetrisi nedeniyle test durumu ( MIN_VALUE = -10, MAX_VALUE = 10) için çalışır MIN_VALUE == -MAX_VALUE, ki bu ikinin tamamlayıcı tamsayıları için geçerli değildir. Gerçekte MIN_VALUE == -MAX_VALUE - 1,.

scala> (java.lang.Integer.MIN_VALUE, java.lang.Integer.MAX_VALUE)
res0: (Int, Int) = (-2147483648,2147483647)

scala> (java.lang.Long.MIN_VALUE, java.lang.Long.MAX_VALUE)
res1: (Long, Long) = (-9223372036854775808,9223372036854775807)

Doğruya uygulandığında MIN_VALUEve MAX_VALUEJohn Kugelman'ın cevabı, ne zaman a == -1ve b ==başka herhangi bir şeyde bir taşma durumu ortaya çıkarır (ilk olarak Kyle tarafından ortaya atılan nokta). İşte düzeltmenin bir yolu:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if ((a == -1 && b == Long.MIN_VALUE) ||
    (a != -1 && a != 0 && ((b > 0 && b > maximum / a) ||
                           (b < 0 && b < maximum / a))))
{
    // Overflow
}

Herhangi için genel bir çözüm değil MIN_VALUEve MAX_VALUEfakat Java'nın için geneldir Longve Integerve herhangi bir değer ave b.


Gereksiz yere karmaşık hale getireceğini düşündüm, çünkü bu çözüm MIN_VALUE = -MAX_VALUE - 1başka herhangi bir durumda değilse işe yarıyor (örnek test durumunuz dahil). Çok değişmem gerekecek.
Jim Pivarski

1
Orijinal posterin ihtiyaçlarının ötesinde nedenlerle; daha genel bir durum için bir çözüme ihtiyaç duydukları için bu sayfayı bulan benim gibi insanlar için (negatif sayılarla uğraşmak ve kesinlikle Java 8 değil). Aslında, bu çözüm saf aritmetik ve mantığın ötesinde herhangi bir işlev içermediğinden, C veya diğer diller için de kullanılabilir.
Jim Pivarski

1

Olabilir:

if(b!= 0 && a * b / b != a) //overflow

Bu "çözümden" emin değilim.

Düzenleme: b! = 0 eklendi.

Olumsuz oy kullanmadan önce : a * b / b optimize edilmeyecektir. Bu derleyici hatası olacaktır. Hala taşma hatasının maskelenebileceği bir durum göremiyorum.


Ayrıca taşma mükemmel bir döngüye neden olduğunda başarısız olur.
Stefan Kendall

Ne demek istediğine dair bir örnek var mı?
Thomas Jung

Küçük bir test yazdım: BigInteger kullanmak, bu bölme yaklaşımını kullanmaktan 6 kat daha yavaştır. Bu nedenle, performans açısından, köşe vakaları için ek kontrollerin buna değer olduğunu varsayıyorum.
mhaller

Java derleyicileri hakkında fazla bir şey bilmiyorum, ancak benzer bir ifade a * b / b, adiğer birçok bağlamda optimize edilebilir .
SingleNegationElimination

TokenMacGuy - taşma tehlikesi varsa, bu şekilde optimize edilemez.
Tom Hawtin - tackline

1

Belki bu size yardımcı olur:

/**
 * @throws ArithmeticException on integer overflow
 */
static long multiply(long a, long b) {
    double c = (double) a * b;
    long d = a * b;

    if ((long) c != d) {
        throw new ArithmeticException("int overflow");
    } else {
        return d;
    }
}

İşlenenlerden biri yardım etmeyecek long.
Tom Hawtin - tackline

1
Bunu test ettin mi? A & b'nin büyük ancak taşmayan değerleri için, çarpmanın ikili sürümündeki yuvarlama hataları nedeniyle bu başarısız olacaktır (örneğin 123456789123L ve 74709314L'yi deneyin). Makine aritmetiğini anlamıyorsanız, bu tür kesin bir sorunun cevabını tahmin etmek, cevap vermekten daha kötüdür çünkü insanları yanıltacaktır.
Zengin

-1

c / c ++ (uzun * uzun):

const int64_ w = (int64_) a * (int64_) b;    
if ((long) (w >> sizeof(long) * 8) != (long) w >> (sizeof(long) * 8 - 1))
    // overflow

java (int * int, üzgünüm java'da int64 bulamadım):

const long w = (long) a * (long) b;    
int bits = 32; // int is 32bits in java    
if ( (int) (w >> bits) != (int) (w >> (bits - 1))) {
   // overflow
}

1. sonucu büyük türe kaydedin (int * int sonucu long, long * long, int64'e koyun)

2.cmp sonucu >> bitler ve sonuç >> (bit - 1)

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.