ฉันจะตรวจสอบได้อย่างไรว่าการคูณสองตัวเลขใน Java จะทำให้ล้นหรือไม่


101

ฉันต้องการจัดการกรณีพิเศษที่การคูณสองจำนวนเข้าด้วยกันทำให้เกิดการล้น รหัสมีลักษณะดังนี้:

int a = 20;
long b = 30;

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

นั่นเป็นเวอร์ชันที่เรียบง่าย ในโปรแกรมจริงaและbมีที่มาจากที่อื่นในรันไทม์ สิ่งที่ฉันต้องการบรรลุมีดังนี้:

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

คุณจะแนะนำให้ฉันตั้งรหัสนี้ได้อย่างไร

อัปเดต: aและbมักจะไม่เป็นลบในสถานการณ์ของฉัน


7
มันเลวร้ายเกินไปว่า Java ไม่ได้ให้ทางอ้อมไปของ CPU ธงล้นเช่นจะทำใน C #
Drew Noakes

คำตอบ:


93

Java 8 มีMath.multiplyExact, Math.addExactฯลฯ สำหรับ ints และระยะยาว สิ่งเหล่านี้ทำให้เกิดการArithmeticExceptionล้น


59

หากaและbเป็นบวกทั้งคู่คุณสามารถใช้:

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

หากคุณต้องการจัดการกับตัวเลขทั้งบวกและลบมันซับซ้อนกว่านี้:

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
}

นี่คือตารางเล็ก ๆ ที่ฉันตีขึ้นเพื่อตรวจสอบสิ่งนี้โดยแกล้งทำเป็นว่าล้นเกิดขึ้นที่ -10 หรือ +10:

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

ฉันควรพูดถึงว่า a และ b ไม่เป็นลบในสถานการณ์ของฉันเสมอซึ่งจะทำให้แนวทางนี้ง่ายขึ้นบ้าง
Steve McLeod

3
ฉันคิดว่านี่อาจล้มเหลวในกรณี: a = -1 และ b = 10 ค่าสูงสุด / นิพจน์ส่งผลให้เป็นจำนวนเต็ม.MIN_VALUEและตรวจพบการล้นเมื่อไม่มีอยู่
Kyle

นี่เป็นสิ่งที่ดีจริงๆ สำหรับผู้ที่สงสัยเหตุผลที่งานนี้เป็นที่สำหรับจำนวนเต็มn, เป็นเช่นเดียวกับn > x n > floor(x)สำหรับการหารจำนวนเต็มบวกจะเป็นการปูพื้นโดยปริยาย (สำหรับจำนวนลบจะปัดเศษขึ้นแทน)
Thomas Ahle

ไปยังที่อยู่a = -1และb = 10ปัญหาดูคำตอบของฉันด้านล่าง
Jim Pivarski

17

มีไลบรารี Java ที่ให้การดำเนินการทางคณิตศาสตร์ที่ปลอดภัยซึ่งตรวจสอบการล้น / การไหลที่ไม่ยาว ตัวอย่างเช่นLongMath.checkedMultiplyของ Guava (long a, long b)จะส่งคืนผลคูณของaและbหากไม่ล้นและพ่นArithmeticExceptionหากมีจำนวนมากเกินไปa * bในlongเลขคณิตที่ลงนาม


4
นี่คือคำตอบที่ดีที่สุด - ใช้ไลบรารีที่ใช้งานโดยผู้ที่เข้าใจเลขคณิตของเครื่องใน Java และได้รับการทดสอบโดยผู้คนจำนวนมาก อย่าพยายามเขียนของคุณเองหรือใช้โค้ดที่ยังไม่ผ่านการทดสอบที่โพสต์ไว้ในคำตอบอื่น ๆ !
รวย

@Enerccio - ฉันไม่เข้าใจความคิดเห็นของคุณ คุณกำลังบอกว่าฝรั่งใช้ไม่ได้กับทุกระบบ? ฉันรับรองได้ว่ามันจะใช้งานได้ทุกที่ที่ Java ทำ คุณกำลังบอกว่าการใช้โค้ดซ้ำเป็นความคิดที่ไม่ดีโดยทั่วไปหรือไม่? ฉันไม่เห็นด้วยถ้าเป็นเช่นนั้น
รวย

2
@Rich ฉันกำลังบอกว่าการรวมไลบรารีขนาดใหญ่เพื่อให้คุณสามารถใช้ฟังก์ชันเดียวเป็นความคิดที่ไม่ดี
Enerccio

ทำไม? หากคุณกำลังเขียนแอปพลิเคชันขนาดใหญ่เช่นสำหรับธุรกิจ JAR พิเศษบน classpath จะไม่ทำอันตรายใด ๆ และ Guava มีโค้ดที่มีประโยชน์มากมายอยู่ในนั้น การใช้โค้ดที่ทดสอบอย่างรอบคอบซ้ำจะดีกว่าการพยายามเขียนเวอร์ชันเดียวกันของคุณเอง (ซึ่งฉันคิดว่าเป็นสิ่งที่คุณแนะนำ?) หากคุณกำลังเขียนในสภาพแวดล้อมที่ JAR พิเศษจะมีราคาแพงมาก (ที่ฝัง Java?) บางทีคุณควรแยกคลาสนี้จาก Guava การคัดลอกคำตอบที่ยังไม่ทดสอบจาก StackOverflow ดีกว่าการคัดลอกโค้ดที่ทดสอบอย่างรอบคอบของ Guava หรือไม่
รวย

ไม่ได้โยนข้อยกเว้นมากเกินไปสำหรับบางสิ่งบางอย่างที่มีเงื่อนไขถ้าสามารถจัดการได้หรือไม่?
victtim

6

คุณสามารถใช้ java.math.BigInteger แทนและตรวจสอบขนาดของผลลัพธ์ (ยังไม่ได้ทดสอบโค้ด):

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
ฉันพบว่าวิธีแก้ปัญหานี้ค่อนข้างช้า
nothrow

แม้ว่าอาจเป็นวิธีที่ดีที่สุด ฉันคิดว่านี่เป็นแอปพลิเคชันตัวเลขซึ่งเป็นสาเหตุที่ฉันไม่แนะนำให้ใช้งานทันที แต่นี่อาจเป็นวิธีที่ดีที่สุดในการแก้ปัญหานี้
Stefan Kendall

2
ฉันไม่แน่ใจว่าคุณสามารถใช้โอเปอเรเตอร์ ">" กับ BigInteger ได้ ควรใช้วิธีการ CompareTo
ปิแอร์

เปลี่ยนเป็น CompareTo และความเร็วอาจมีความสำคัญหรืออาจไม่ขึ้นอยู่กับสถานการณ์ที่จะใช้รหัส
Ulf Lindback

5

ใช้ลอการิทึมเพื่อตรวจสอบขนาดของผลลัพธ์


คุณหมายถึง: ceil(log(a)) + ceil(log(b)) > log(Long.MAX)?
Thomas Jung

1
ฉันตรวจสอบแล้ว สำหรับค่าขนาดเล็กจะเร็วกว่า BigInteger 20% และสำหรับค่าที่ใกล้ MAX จะใกล้เคียงกัน (เร็วขึ้น 5%) โค้ดของ Yossarian นั้นเร็วที่สุด (เร็วกว่า BigInteger 95% และ 75%)
Thomas Jung

ฉันค่อนข้างสงสัยว่ามันอาจล้มเหลวในบางกรณี
Tom Hawtin - แทคไลน์

โปรดจำไว้ว่าบันทึกจำนวนเต็มมีประสิทธิภาพเพียงแค่การนับจำนวนศูนย์นำหน้าและคุณสามารถเพิ่มประสิทธิภาพกรณีทั่วไปบางอย่าง (เช่น ((a | b) & 0xffffffff00000000L) == 0) คุณรู้ว่าคุณปลอดภัย) ในทางกลับกันเว้นแต่คุณจะได้รับการปรับให้เหมาะสมลงเหลือ 30/40-ish clock cycles สำหรับกรณีทั่วไปวิธีของ John Kugelman อาจทำงานได้ดีกว่า (การหารจำนวนเต็มคือ c 2 บิต / รอบนาฬิกาตามที่ฉันจำได้)
Neil Coffey

PS Sorr ฉันคิดว่าฉันต้องการชุดบิตพิเศษในหน้ากาก AND (0xffffffff80000000L) - มันช้าไปหน่อย แต่คุณเข้าใจแล้ว ...
Neil Coffey

4

Java มีบางอย่างเช่น int.MaxValue หรือไม่? ถ้าใช่ก็ลองดู

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

แก้ไข: เห็น Long.MAX_VALUE ที่มีปัญหา


ฉันไม่ downvote แต่Math.Abs(a)ไม่ทำงานถ้าเป็นa Long.MIN_VALUE
John Kugelman

@ จอห์น - a และ b คือ> 0 ฉันคิดว่าแนวทางของ Yossarian (b! = 0 && a> Long.MAX_VALUE / b) ดีที่สุด
Thomas Jung

@ โทมัส a และ b คือ> = 0 นั่นคือไม่เป็นลบ
Steve McLeod

2
ถูกต้อง แต่ในกรณีนั้นไม่จำเป็นต้องมี Abs หากอนุญาตให้ใช้ตัวเลขติดลบจะล้มเหลวอย่างน้อยหนึ่งกรณีขอบ นั่นคือทั้งหมดที่ฉันพูดแค่เป็นคนขี้เกียจ
John Kugelman

ใน Java คุณต้องใช้ Math.abs ไม่ใช่ Math.Abs ​​(C # guy?)
dfa

4

นี่คือวิธีที่ง่ายที่สุดที่ฉันคิดได้

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

ขโมยมาจากป่าช้า

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

อัปเดต: รหัสนี้สั้นและใช้งานได้ดี อย่างไรก็ตามมันล้มเหลวสำหรับ a = -1, b = Long.MIN_VALUE

การปรับปรุงที่เป็นไปได้อย่างหนึ่ง:

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

โปรดทราบว่าสิ่งนี้จะทำให้เกิดการล้นบางส่วนโดยไม่มีการแบ่งส่วนใด ๆ


คุณสามารถใช้ Long.signum แทน Math.signum
aditsu เลิกเพราะ SE คือ EVIL

3

ดังที่ได้กล่าวไปแล้ว Java 8 มีเมธอด Math.xxxExact ที่ทำให้เกิดข้อยกเว้นในการโอเวอร์โฟลว์

หากคุณไม่ได้ใช้ Java 8 สำหรับโปรเจ็กต์ของคุณคุณยังสามารถ "ยืม" การนำไปใช้งานได้ซึ่งค่อนข้างกะทัดรัด

ต่อไปนี้คือลิงก์บางส่วนไปยังการใช้งานเหล่านี้ในที่เก็บซอร์สโค้ด JDK ไม่รับประกันว่าสิ่งเหล่านี้จะยังคงใช้ได้หรือไม่ แต่ในกรณีใด ๆ คุณควรจะสามารถดาวน์โหลดซอร์ส JDK และดูว่าพวกเขาใช้เวทมนตร์ในjava.lang.Mathคลาสได้อย่างไร

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

ฯลฯ ฯลฯ

อัปเดต: เปลี่ยนลิงก์ที่ไม่ถูกต้องไปยังเว็บไซต์ของบุคคลที่สามเป็นลิงก์ไปยังที่เก็บ Mercurial ของ Open JDK


2

ฉันไม่แน่ใจว่าทำไมไม่มีใครมองหาวิธีแก้ปัญหาเช่น:

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

เลือกตัวเลขสองตัวที่จะใหญ่กว่า


2
ฉันไม่คิดว่ามันจะสำคัญถ้าaยิ่งใหญ่หรือเล็ก?
Thomas Ahle

2

ฉันต้องการสร้างคำตอบของ John Kugelman โดยไม่ต้องแทนที่ด้วยการแก้ไขโดยตรง มันใช้ได้กับกรณีทดสอบของเขา ( MIN_VALUE = -10, MAX_VALUE = 10) เนื่องจากความสมมาตรMIN_VALUE == -MAX_VALUEซึ่งไม่ใช่กรณีของจำนวนเต็มบวกของสองตัว ในความเป็นจริง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)

เมื่อนำไปใช้กับจริงMIN_VALUEและMAX_VALUEคำตอบของ John Kugelman จะให้กรณีล้นเมื่อใดa == -1และb ==สิ่งอื่นใด (ชี้ก่อนโดย Kyle) นี่คือวิธีแก้ไข:

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
}

มันไม่ได้เป็นวิธีการแก้ปัญหาทั่วไปสำหรับการใด ๆMIN_VALUEและMAX_VALUEแต่มันเป็นข้อมูลทั่วไปของ Java LongและIntegerและความคุ้มค่าของการใด ๆและab


ฉันแค่คิดว่ามันจะซับซ้อนโดยไม่จำเป็นเนื่องจากวิธีนี้ใช้ได้ผลก็ต่อเมื่อMIN_VALUE = -MAX_VALUE - 1ไม่ใช่กรณีอื่น ๆ (รวมถึงกรณีทดสอบตัวอย่างของคุณ) ฉันต้องเปลี่ยนไปมาก
Jim Pivarski

1
ด้วยเหตุผลที่เกินความต้องการของผู้โพสต์ต้นฉบับ; สำหรับคนอย่างฉันที่พบหน้านี้เพราะต้องการวิธีแก้ปัญหาสำหรับกรณีทั่วไป (จัดการตัวเลขติดลบไม่ใช่ Java 8 อย่างเคร่งครัด) ในความเป็นจริงเนื่องจากโซลูชันนี้ไม่เกี่ยวข้องกับฟังก์ชันใด ๆ นอกเหนือจากเลขคณิตและตรรกะที่บริสุทธิ์จึงสามารถใช้กับภาษา C หรือภาษาอื่น ๆ ได้เช่นกัน
Jim Pivarski

1

อาจจะ:

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

ไม่แน่ใจเกี่ยวกับ "วิธีแก้ปัญหา" นี้

แก้ไข: เพิ่ม b! = 0

ก่อนที่คุณจะลงคะแนน : a * b / b จะไม่ได้รับการปรับให้เหมาะสม นี่จะเป็นบั๊กของคอมไพเลอร์ ฉันยังไม่เห็นกรณีที่สามารถปกปิดข้อบกพร่องล้นได้


ยังล้มเหลวเมื่อโอเวอร์โฟลว์ทำให้เกิดลูปที่สมบูรณ์แบบ
Stefan Kendall

คุณมีตัวอย่างของสิ่งที่คุณหมายถึงหรือไม่?
Thomas Jung

เพิ่งเขียนการทดสอบเล็กน้อย: การใช้ BigInteger ช้ากว่าการใช้แนวทางการแบ่งนี้ 6 เท่า ดังนั้นฉันถือว่าการตรวจสอบเพิ่มเติมสำหรับกรณีที่เข้ามุมนั้นคุ้มค่ากับประสิทธิภาพที่ชาญฉลาด
mhaller

ไม่รู้เกี่ยวกับคอมไพเลอร์ java มากนัก แต่นิพจน์เช่นa * b / bนี้น่าจะได้รับการปรับให้เหมาะสมกับaบริบทอื่น ๆ
SingleNegationElimination

TokenMacGuy - ไม่สามารถปรับให้เหมาะสมเช่นนั้นได้หากมีอันตรายจากการล้น
Tom Hawtin - แทคไลน์

1

บางทีนี่อาจช่วยคุณได้:

/**
 * @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;
    }
}

longช่วยเหลือเคยชินเป็นหนึ่งในตัวถูกดำเนินการคือ
Tom Hawtin - แทคไลน์

1
คุณได้ทดสอบสิ่งนี้หรือไม่? สำหรับค่า a & b ที่มาก แต่ไม่มากเกินไปสิ่งนี้จะล้มเหลวเนื่องจากข้อผิดพลาดในการปัดเศษในเวอร์ชันสองเท่าของการคูณ (ลองเช่น 123456789123L และ 74709314L) หากคุณไม่เข้าใจเรื่องเลขคณิตของเครื่องจักรการเดาคำตอบสำหรับคำถามที่แม่นยำแบบนี้แย่กว่าการไม่ตอบเพราะจะทำให้คนเข้าใจผิด
รวย

-1

c / c ++ (ยาว * ยาว):

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

java (int * int ขออภัยฉันไม่พบ int64 ใน java):

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. บันทึกผลลัพธ์ในประเภทขนาดใหญ่ (int * int ทำให้ผลลัพธ์เป็น long, long * long ใส่ไปที่ int64)

ผลลัพธ์ 2.cmp >> บิตและผลลัพธ์ >> (บิต - 1)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.