Java จัดการกับ underflow และ overflows จำนวนเต็มอย่างไรและคุณจะตรวจสอบได้อย่างไร


226

Java จัดการกับจำนวนเต็มอันเดอร์โฟลว์และโอเวอร์โฟลว์อย่างไร

จากนั้นคุณจะตรวจสอบ / ทดสอบว่าสิ่งนี้เกิดขึ้นได้อย่างไร


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

@DrewNoakes และมันก็แย่เกินไปที่ C # จะไม่เริ่มต้นcheckedเท่าที่ฉันรู้ ฉันไม่เห็นมันใช้อะไรมากและการพิมพ์checked { code; }นั้นใช้งานได้ดีเท่ากับวิธีเรียก
Maarten Bodewes

2
@MaartenBodewes คุณสามารถตั้งเป็นค่าเริ่มต้นในระหว่างการรวบรวมแอสเซมบลี csc /checked ...หรือตั้งค่าคุณสมบัติในบานหน้าต่างคุณสมบัติของโครงการใน Visual Studio
Drew Noakes

@DrewNoakes ตกลงน่าสนใจ ค่อนข้างแปลกที่มันเป็นการตั้งค่านอกรหัสแม้ว่า โดยทั่วไปฉันต้องการมีพฤติกรรมแบบเดียวกันของโปรแกรมโดยไม่คำนึงถึงการตั้งค่าดังกล่าว (อาจยกเว้นการยืนยัน)
Maarten Bodewes

@MaartenBodewes ฉันคิดว่าเหตุผลคือมีค่าใช้จ่ายในการตรวจสอบ ดังนั้นบางทีคุณอาจเปิดใช้งานในการดีบักบิวด์จากนั้นปิดการใช้งานในบิวด์รีลีสเช่นเดียวกับการยืนยันประเภทอื่น ๆ
Drew Noakes

คำตอบ:


217

ถ้ามันล้นก็จะกลับไปที่ค่าต่ำสุดและดำเนินการต่อจากที่นั่น ถ้ามันต่ำกว่าค่ามันจะกลับไปที่ค่าสูงสุดและดำเนินการต่อจากตรงนั้น

คุณสามารถตรวจสอบก่อนได้ดังนี้:

public static boolean willAdditionOverflow(int left, int right) {
    if (right < 0 && right != Integer.MIN_VALUE) {
        return willSubtractionOverflow(left, -right);
    } else {
        return (~(left ^ right) & (left ^ (left + right))) < 0;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    if (right < 0) {
        return willAdditionOverflow(left, -right);
    } else {
        return ((left ^ right) & (left ^ (left - right))) < 0;
    }
}

(คุณสามารถแทนที่intด้วยlongเพื่อทำการตรวจสอบเดียวกันสำหรับlong)

หากคุณคิดว่าสิ่งนี้อาจเกิดขึ้นบ่อยครั้งให้ลองใช้ประเภทข้อมูลหรือวัตถุที่สามารถเก็บค่าที่ใหญ่กว่าเช่นlongหรืออาจจะjava.math.BigIntegerเป็น อันสุดท้ายไม่ได้ล้นหน่วยความจำ JVM ที่มีอยู่คือขีด จำกัด


หากคุณเคยอยู่บน Java8 มาก่อนแล้วคุณสามารถใช้ประโยชน์จากวิธีการใหม่Math#addExact()และMath#subtractExact()วิธีการที่จะทำให้เกิดการArithmeticExceptionล้น

public static boolean willAdditionOverflow(int left, int right) {
    try {
        Math.addExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    try {
        Math.subtractExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

ซอร์สโค้ดสามารถพบได้ที่นี่และที่นี่ตามลำดับ

แน่นอนคุณสามารถใช้มันได้ทันทีแทนที่จะซ่อนไว้ในbooleanวิธีการยูทิลิตี้


13
@dhblah สมมติว่าค่าสูงสุดและต่ำสุดที่ Java อนุญาตสำหรับ int คือ+100, -100ตามลำดับ หากคุณเพิ่มหนึ่งลงในจำนวนเต็ม Java กระบวนการจะมีลักษณะเช่นนี้ในขณะที่มันล้น 98, 99, 100, -100, -99, -98, .... นั่นทำให้รู้สึกมากขึ้น?
ออสติน

6
ฉันแนะนำให้ใช้วิธีอรรถประโยชน์แทนการใช้รหัสทันที วิธีการยูทิลิตี้เป็นแบบดั้งเดิมและจะถูกแทนที่ด้วยรหัสเฉพาะเครื่อง การทดสอบอย่างรวดเร็วแสดงให้เห็นว่า Math.addExact นั้นเร็วกว่าวิธีที่คัดลอก 30% (Java 1.8.0_40)
TilmannZ

1
@ErikE Math#addExactเป็นไวยากรณ์ที่ใช้ตามปกติเมื่อเขียน javadocs - ในขณะที่ปกติจะถูกแปลงเป็นMath.addExactบางครั้งรูปแบบอื่น ๆ เพียงแค่ติดรอบ
Pokechu22

1
If it underflows, it goes back to the maximum value and continues from there.- ดูเหมือนว่าคุณสับสนอันเดอร์โฟลว์กับการลบล้น underflow ในจำนวนเต็มเกิดขึ้นตลอดเวลา (เมื่อผลลัพธ์คือเศษส่วน)
โบสถ์

1
en.wikipedia.org/wiki/Arithmetic_underflowกล่าวว่า Underflow เป็นเงื่อนไขในโปรแกรมคอมพิวเตอร์ที่ผลลัพธ์ของการคำนวณเป็นจำนวนค่าสัมบูรณ์ที่น้อยกว่าคอมพิวเตอร์จริง ๆ สามารถแสดงในหน่วยความจำบนซีพียูได้ ดังนั้นอันเดอร์โฟล์ไม่ได้ใช้กับ Java Integers @BalusC
Jingguo Yao

66

เท่าที่เป็นไปได้จำนวนเต็มชนิดจาวาไม่จัดการกับ / Underflow เลย (สำหรับการลอยตัวและพฤติกรรมที่แตกต่างกันสองครั้งมันจะล้างไป +/- อินฟินิตี้เช่นเดียวกับเอกสาร IEEE-754)

เมื่อเพิ่มสอง int คุณจะไม่ได้รับการบ่งชี้ว่าเกิดโอเวอร์โฟลว์ วิธีง่าย ๆ ในการตรวจสอบการโอเวอร์โฟลว์คือการใช้ชนิดที่ใหญ่กว่าถัดไปเพื่อทำการดำเนินการจริงและตรวจสอบว่าผลลัพธ์ยังอยู่ในช่วงของประเภทแหล่งที่มาหรือไม่:

public int addWithOverflowCheck(int a, int b) {
    // the cast of a is required, to make the + work with long precision,
    // if we just added (a + b) the addition would use int precision and
    // the result would be cast to long afterwards!
    long result = ((long) a) + b;
    if (result > Integer.MAX_VALUE) {
         throw new RuntimeException("Overflow occured");
    } else if (result < Integer.MIN_VALUE) {
         throw new RuntimeException("Underflow occured");
    }
    // at this point we can safely cast back to int, we checked before
    // that the value will be withing int's limits
    return (int) result;
}

สิ่งที่คุณจะทำแทนการส่งคำสั่งขึ้นอยู่กับข้อกำหนดของแอพพลิเคชั่นของคุณ (การโยนล้างไปจนถึงต่ำสุด / สูงสุดหรือเพียงแค่บันทึกสิ่งใดก็ตาม) หากคุณต้องการตรวจจับการไหลล้นในการดำเนินงานที่ยาวนานคุณไม่มีโชคเลยที่จะต้องใช้ BigInteger แทน


แก้ไข (2014-05-21): เนื่องจากคำถามนี้ดูเหมือนจะถูกอ้างถึงบ่อยครั้งและฉันต้องแก้ปัญหาเดียวกันด้วยตัวเองมันค่อนข้างง่ายที่จะประเมินสภาพโอเวอร์โฟลว์ด้วยวิธีเดียวกับที่ CPU จะคำนวณค่าสถานะ V

โดยทั่วไปแล้วมันเป็นนิพจน์บูลีนที่เกี่ยวข้องกับเครื่องหมายของตัวถูกดำเนินการทั้งสองรวมถึงผลลัพธ์:

/**
 * Add two int's with overflow detection (r = s + d)
 */
public static int add(final int s, final int d) throws ArithmeticException {
    int r = s + d;
    if (((s & d & ~r) | (~s & ~d & r)) < 0)
        throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");    
    return r;
}

ใน java มันง่ายกว่าที่จะใช้การแสดงออก (ใน if) กับทั้ง 32 บิตและตรวจสอบผลโดยใช้ <0 (ซึ่งจะทดสอบบิตเครื่องหมายได้อย่างมีประสิทธิภาพ) หลักการทำงานเหมือนกันทุกประการสำหรับประเภทจำนวนเต็มทั้งหมดการเปลี่ยนการประกาศทั้งหมดในวิธีการด้านบนเป็นความยาวทำให้ใช้งานได้นาน

สำหรับประเภทที่มีขนาดเล็กลงเนื่องจากการแปลงโดยนัยเป็น int (ดู JLS สำหรับการดำเนินงานระดับบิตเพื่อดูรายละเอียด) แทนที่จะตรวจสอบ <0 การตรวจสอบต้องปิดบังบิตเครื่องหมายอย่างชัดเจน (0x8000 สำหรับตัวถูกดำเนินการสั้น 0x80 สำหรับตัวถูกดำเนินการไบต์ และการประกาศพารามิเตอร์ที่เหมาะสม):

/**
 * Subtract two short's with overflow detection (r = d - s)
 */
public static short sub(final short d, final short s) throws ArithmeticException {
    int r = d - s;
    if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
        throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
    return (short) r;
}

(โปรดทราบว่าตัวอย่างด้านบนใช้นิพจน์ที่จำเป็นสำหรับการลบการตรวจจับล้น)


นิพจน์บูลีนเหล่านี้ทำงานอย่างไร / ทำไม ครั้งแรกบางคนคิดเชิงตรรกะเผยให้เห็นว่าสามารถล้นเพียงเกิดขึ้นหากมีสัญญาณของการขัดแย้งทั้งสองจะเหมือนกัน เพราะถ้าอาร์กิวเมนต์หนึ่งเป็นลบและบวกหนึ่งผลลัพธ์ (ของเพิ่ม) จะต้องใกล้เคียงกับศูนย์หรือในกรณีที่รุนแรงอาร์กิวเมนต์หนึ่งคือศูนย์เหมือนกันกับอาร์กิวเมนต์อื่น เนื่องจากข้อโต้แย้งด้วยตัวเองไม่สามารถสร้างเงื่อนไขโอเวอร์โฟลได้ผลรวมของพวกเขาจึงไม่สามารถสร้างโอเวอร์โฟลว์ได้เช่นกัน

แล้วจะเกิดอะไรขึ้นถ้าทั้งสองข้อโต้แย้งมีสัญลักษณ์เหมือนกัน? ให้ดูที่ทั้งสองกรณีเป็นบวก: การเพิ่มสองอาร์กิวเมนต์ที่สร้างผลรวมที่มีขนาดใหญ่กว่าประเภท MAX_VALUE จะให้ผลเป็นค่าลบเสมอดังนั้นการล้นจะเกิดขึ้นหาก arg1 + arg2> MAX_VALUE ตอนนี้ค่าสูงสุดที่อาจส่งผลให้คือ MAX_VALUE + MAX_VALUE (กรณีสุดขั้วทั้งสองอาร์กิวเมนต์คือ MAX_VALUE) สำหรับไบต์ (ตัวอย่าง) ที่จะหมายถึง 127 + 127 = 254 การดูการแทนค่าบิตของค่าทั้งหมดที่อาจเป็นผลมาจากการเพิ่มค่าบวกสองค่าหนึ่งพบว่าค่าที่ล้น (128 ถึง 254) ทั้งหมดมีบิต 7 ชุดในขณะที่ ทั้งหมดที่ไม่ล้น (0 ถึง 127) ได้ล้างบิต 7 (บนสุด, เครื่องหมาย) นั่นเป็นสิ่งที่ส่วนแรก (ขวา) ของการแสดงออกตรวจสอบ:

if (((s & d & ~r) | (~s & ~d & r)) < 0)

(~ s & ~ d & r) กลายเป็นจริงเฉพาะในกรณีที่ทั้งตัวถูกดำเนินการ (s, d) เป็นบวกและผลลัพธ์ (r) เป็นลบ (นิพจน์ทำงานได้กับ 32 บิตทั้งหมด แต่บิตเดียวที่เราสนใจ เป็นบิตสูงสุด (เครื่องหมาย) ซึ่งถูกตรวจสอบโดย <0)

ตอนนี้ถ้าอาร์กิวเมนต์ทั้งคู่เป็นลบผลรวมของพวกเขาจะไม่มีวันใกล้ศูนย์มากกว่าข้อโต้แย้งใด ๆ ผลรวมจะต้องใกล้กับลบอนันต์ ค่าที่มากที่สุดที่เราสามารถสร้างได้คือ MIN_VALUE + MIN_VALUE ซึ่ง (อีกครั้งสำหรับตัวอย่างไบต์) แสดงให้เห็นว่าสำหรับค่าใด ๆ ในช่วง (-1 ถึง -128) เครื่องหมายบิตจะถูกตั้งค่าในขณะที่ค่าล้นใด ๆ ที่เป็นไปได้ (-129 ถึง -256 ) มีการล้างบิตเครื่องหมาย ดังนั้นสัญญาณของผลลัพธ์จะเผยให้เห็นสภาพล้น นั่นคือสิ่งที่ครึ่งซ้าย (s & d & ~ r) ตรวจสอบกรณีที่อาร์กิวเมนต์ทั้งสอง (s, d) เป็นค่าลบและผลลัพธ์ที่เป็นค่าบวก ตรรกะนั้นส่วนใหญ่เทียบเท่ากับกรณีที่เป็นบวก รูปแบบบิตทั้งหมดที่อาจเป็นผลมาจากการเพิ่มค่าลบสองค่าจะมีการล้างบิตเครื่องหมายถ้าหากเกิดความไม่เพียงพอ


1
คุณสามารถตรวจสอบกับผู้ประกอบการระดับบิตเช่นกันดีกว่าloglog.com/roger/2011/05/…
rogerdpack

1
สิ่งนี้จะใช้ได้ แต่ฉันสมมติว่ามันจะมีผลงานที่น่ารังเกียจ
chessofnerd

33

โดยค่าเริ่มต้นคณิตศาสตร์ int และ long ของ Java จะล้อมรอบบนโอเวอร์โฟลว์และอันเดอร์โฟลว์อย่างเงียบ ๆ (การดำเนินการจำนวนเต็มในประเภทจำนวนเต็มอื่น ๆ ดำเนินการโดยการเลื่อนตัวถูกดำเนินการไปยัง int หรือ long ตามJLS 4.2.2 )

ในฐานะของ Java 8, java.lang.Mathให้addExact, subtractExact, multiplyExact, incrementExact, decrementExactและnegateExactวิธีการแบบคงที่สำหรับทั้ง int และข้อโต้แย้งมานานแล้วว่าดำเนินการตั้งชื่อ, การขว้างปา ArithmeticException บนล้น (ไม่มีวิธีการหารอย่างแน่นอน - คุณจะต้องตรวจสอบกรณีพิเศษหนึ่งตัว ( MIN_VALUE / -1) ด้วยตนเอง)

ในฐานะของ Java 8, java.lang.Math ยังจัดให้มีtoIntExactการลอง long to int โดยการโยน ArithmeticException หากค่าของ long ไม่พอดีกับ int สิ่งนี้มีประโยชน์สำหรับการคำนวณผลรวมของ int โดยใช้คณิตศาสตร์แบบไม่ จำกัด ที่ทำเครื่องหมายแล้วใช้toIntExactเพื่อส่งไปยัง int ในตอนท้าย (แต่ระวังอย่าให้ผลรวมล้น)

หากคุณยังคงใช้ Java เวอร์ชันเก่ากว่า Google Guava ให้วิธีการคงที่IntMath และ LongMathสำหรับการบวกการลบการคูณและการยกกำลังที่เลือกไว้ (ตรวจสอบการโอเวอร์โฟลว์ ) คลาสเหล่านี้ยังมีวิธีการคำนวณแฟคทอเรียลและสัมประสิทธิ์ทวินามที่กลับมาMAX_VALUEจากการล้น (ซึ่งไม่สะดวกในการตรวจสอบ) ฝรั่งของอาคารเรียนดั้งเดิมSignedBytes, UnsignedBytes, ShortsและIntsให้checkedCastวิธีการในการกวดขันประเภทขนาดใหญ่ (โยน IllegalArgumentException ภายใต้ / ล้นไม่ ArithmeticException) เช่นเดียวกับsaturatingCastวิธีการที่ผลตอบแทนMIN_VALUEหรือMAX_VALUEบนล้น


32

Java ไม่ได้ทำอะไรกับจำนวนเต็มล้นสำหรับ int หรือยาวประเภทดั้งเดิมและละเว้นล้นด้วยจำนวนเต็มบวกและลบ

คำตอบนี้ก่อนอธิบายถึงจำนวนเต็มล้นแสดงตัวอย่างว่ามันจะเกิดขึ้นได้อย่างไรแม้จะมีค่ากลางในการประเมินผลการแสดงออกแล้วให้เชื่อมโยงไปยังทรัพยากรที่ให้เทคนิครายละเอียดสำหรับการป้องกันและตรวจสอบล้นจำนวนเต็ม

เลขคณิตและนิพจน์จำนวนเต็มที่ส่งผลให้เกิดโอเวอร์โฟลว์ที่ไม่คาดคิดหรือไม่ถูกตรวจพบเป็นข้อผิดพลาดทั่วไปในการเขียนโปรแกรม ปัญหาจำนวนเต็มล้นที่ไม่คาดคิดหรือไม่ถูกตรวจพบก็เป็นปัญหาด้านความปลอดภัยที่สามารถนำไปใช้ประโยชน์โดยเฉพาะอย่างยิ่งเนื่องจากมีผลต่อวัตถุอาร์เรย์สแต็กและรายการ

ล้นสามารถเกิดขึ้นได้ทั้งในทิศทางบวกหรือลบโดยที่ค่าบวกหรือลบจะเกินกว่าค่าสูงสุดหรือต่ำสุดสำหรับประเภทดั้งเดิมในคำถาม ล้นสามารถเกิดขึ้นได้ในค่ากลางในระหว่างการแสดงออกหรือการประเมินผลการดำเนินงานและมีผลต่อผลลัพธ์ของการแสดงออกหรือการดำเนินงานที่คาดว่าค่าสุดท้ายจะอยู่ในช่วง

บางครั้งการลบมากเกินไปจะเรียกว่าอันเดอร์โฟลว์ผิดพลาด อันเดอร์โฟล์คือสิ่งที่จะเกิดขึ้นเมื่อค่าใกล้ศูนย์มากกว่าค่าที่อนุญาต อันเดอร์โฟล์เกิดขึ้นในเลขคณิตจำนวนเต็มและคาดหวัง อันเดอร์โฟลว์จำนวนเต็มเกิดขึ้นเมื่อการประเมินผลจำนวนเต็มจะอยู่ระหว่าง -1 ถึง 0 หรือ 0 และ 1 สิ่งที่จะเป็นเศษส่วนตัดเป็น 0 นี่เป็นเรื่องปกติและคาดว่าจะมีเลขจำนวนเต็มและไม่ถือว่าเป็นข้อผิดพลาด อย่างไรก็ตามอาจทำให้เกิดข้อผิดพลาดในการโยนรหัสได้ ตัวอย่างหนึ่งคือข้อยกเว้น "ArithmeticException: / by zero" หากผลลัพธ์ของอันเดอร์โฟลว์จำนวนเต็มถูกใช้เป็นตัวหารในนิพจน์

พิจารณารหัสต่อไปนี้:

int bigValue = Integer.MAX_VALUE;
int x = bigValue * 2 / 5;
int y = bigValue / x;

ซึ่งส่งผลให้ x ถูกกำหนดเป็น 0 และการประเมินผลที่ตามมาของ bigValue / x จะส่งข้อยกเว้น "ArithmeticException: / by zero" (เช่นหารด้วยศูนย์) แทนที่จะเป็น y ที่กำหนดค่า 2

ผลลัพธ์ที่คาดหวังสำหรับ x คือ 858,993,458 ซึ่งน้อยกว่าค่า int สูงสุดของ 2,147,483,647 อย่างไรก็ตามผลลัพธ์ระดับกลางจากการประเมินค่า Integer.MAX_Value * 2 จะเป็น 4,294,967,294 ซึ่งเกินค่า int สูงสุดและ -2 ตามการแทนค่าจำนวนเต็ม 2 วินาที การประเมินผลที่ตามมาของ -2 / 5 ประเมินเป็น 0 ซึ่งได้รับมอบหมายให้กับ x

การจัดเรียงนิพจน์ใหม่สำหรับการคำนวณ x ไปยังนิพจน์ที่เมื่อประเมินแล้วหารก่อนการคูณรหัสต่อไปนี้:

int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;

ผลลัพธ์ใน x ถูกกำหนด 858,993,458 และ y กำลังมอบหมาย 2 ซึ่งคาดว่า

ผลลัพธ์ระดับกลางจาก bigValue / 5 คือ 429,496,729 ซึ่งไม่เกินค่าสูงสุดสำหรับ int การประเมินที่ตามมาของ 429,496,729 * 2 ไม่เกินค่าสูงสุดสำหรับ int และผลลัพธ์ที่คาดหวังได้รับมอบหมายให้เป็น x การประเมินค่า y จะไม่หารด้วยศูนย์ การประเมินผลสำหรับ x และ y ทำงานตามที่คาดไว้

ค่าจำนวนเต็ม Java ถูกเก็บไว้เป็นและทำงานตาม 2s เสริมสมบูรณ์เป็นตัวแทนจำนวนเต็มลงนาม เมื่อค่าผลลัพธ์จะใหญ่กว่าหรือเล็กกว่าค่าจำนวนเต็มสูงสุดหรือต่ำสุดผลลัพธ์ค่าจำนวนเต็มเสริมของ 2 จะแทน ในสถานการณ์ที่ไม่ได้ออกแบบมาอย่างชัดเจนให้ใช้พฤติกรรมการเติม 2s ซึ่งเป็นสถานการณ์ทางคณิตศาสตร์เลขจำนวนเต็มส่วนใหญ่ค่าเสริม 2s ที่เกิดขึ้นจะทำให้ตรรกะการเขียนโปรแกรมหรือข้อผิดพลาดการคำนวณดังแสดงในตัวอย่างด้านบน บทความวิกิพีเดียที่ยอดเยี่ยมอธิบายถึงเลขจำนวนเต็มไบนารีที่ได้รับการยกย่อง 2 ที่นี่: ส่วนประกอบสองอย่าง - Wikipedia

มีเทคนิคในการหลีกเลี่ยงการล้นของจำนวนเต็มโดยไม่ตั้งใจ Techinques อาจถูกจัดประเภทเป็นการใช้การทดสอบตามสภาพก่อนกำหนด, การ upcasting และ BigInteger

การทดสอบตามเงื่อนไขประกอบด้วยการตรวจสอบค่าที่จะเข้าสู่การดำเนินการทางคณิตศาสตร์หรือนิพจน์เพื่อให้แน่ใจว่าการโอเวอร์โฟลว์จะไม่เกิดขึ้นกับค่าเหล่านั้น การเขียนโปรแกรมและการออกแบบจะต้องสร้างการทดสอบเพื่อให้มั่นใจว่าค่าอินพุตจะไม่ทำให้เกิดการโอเวอร์โฟลว์แล้วกำหนดว่าจะทำอย่างไรถ้าค่าอินพุตเกิดขึ้นซึ่งจะทำให้เกิดการโอเวอร์โฟลว์

Upcasting ประกอบด้วยการใช้ชนิดดั้งเดิมที่มีขนาดใหญ่กว่าเพื่อทำการดำเนินการทางคณิตศาสตร์หรือการแสดงออกและจากนั้นกำหนดว่าค่าผลลัพธ์ที่ได้จะเกินค่าสูงสุดหรือต่ำสุดสำหรับจำนวนเต็ม แม้ว่าจะมีการ upcasting แต่ก็ยังมีความเป็นไปได้ที่ค่าหรือค่ากลางบางอย่างในการดำเนินการหรือการแสดงออกจะเกินค่าสูงสุดหรือต่ำสุดสำหรับประเภท upcast และทำให้เกิด overflow ซึ่งจะไม่ถูกตรวจพบและจะทำให้เกิดผลลัพธ์ที่ไม่คาดคิด ผ่านการวิเคราะห์หรือพรีเงื่อนไขอาจเป็นไปได้ที่จะป้องกันไม่ให้ล้นด้วย upcasting เมื่อการป้องกันโดยไม่ต้อง upcasting เป็นไปไม่ได้หรือในทางปฏิบัติ หากจำนวนเต็มในคำถามนั้นมีความยาวประเภทดั้งเดิมแล้วการถ่ายทอดสัญญาณแบบอัพไม่สามารถทำได้ด้วยประเภทดั้งเดิมใน Java

เทคนิค BigInteger ประกอบด้วยการใช้ BigInteger สำหรับการดำเนินการทางคณิตศาสตร์หรือการแสดงออกโดยใช้วิธีการห้องสมุดที่ใช้ BigInteger BigInteger ไม่ล้น มันจะใช้หน่วยความจำที่มีอยู่ทั้งหมดหากจำเป็น โดยปกติแล้ววิธีการทางคณิตศาสตร์ของมันจะมีประสิทธิภาพน้อยกว่าการดำเนินการจำนวนเต็มเล็กน้อยเท่านั้น เป็นไปได้ว่าผลลัพธ์ที่ใช้ BigInteger อาจเกินค่าสูงสุดหรือต่ำสุดสำหรับจำนวนเต็มอย่างไรก็ตามการล้นจะไม่เกิดขึ้นในการคำนวณทางคณิตศาสตร์ที่นำไปสู่ผลลัพธ์ การเขียนโปรแกรมและการออกแบบยังคงต้องพิจารณาว่าจะทำอย่างไรหากผลลัพธ์ BigInteger เกินกว่าค่าสูงสุดหรือต่ำสุดสำหรับประเภทผลลัพธ์ดั้งเดิมที่ต้องการเช่น int หรือ long

โปรแกรม CERT ของ Carnegie Mellon Software Engineering Institute และ Oracle ได้สร้างชุดของมาตรฐานสำหรับการเขียนโปรแกรม Java ที่ปลอดภัย รวมอยู่ในมาตรฐานเป็นเทคนิคในการป้องกันและตรวจสอบจำนวนเต็มล้น มาตรฐานถูกเผยแพร่เป็นแหล่งข้อมูลออนไลน์ที่สามารถเข้าถึงได้อย่างอิสระที่นี่: มาตรฐานการเข้ารหัส CERT Oracle Secure สำหรับ Java

ส่วนมาตรฐานที่อธิบายและมีตัวอย่างที่ใช้งานได้จริงของเทคนิคการเข้ารหัสเพื่อป้องกันหรือตรวจจับการล้นของจำนวนเต็มอยู่ที่นี่: NUM00-J ตรวจจับหรือป้องกันการล้นจำนวนเต็ม

นอกจากนี้ยังมีแบบฟอร์มหนังสือและ PDF ของมาตรฐาน CERT Oracle Secure Coding สำหรับ Java


นี่เป็นคำตอบที่ดีที่สุดที่นี่เพราะมันระบุอย่างชัดเจนว่าอันเดอร์โฟล์คืออะไร (คำตอบที่ยอมรับไม่ได้) และยังแสดงเทคนิคต่าง ๆ สำหรับการจัดการกับโอเวอร์โฟลว์ / อันเดอร์โฟลด้วย
nave

12

มีเพียง kinda พบปัญหานี้ด้วยตัวเองนี่คือทางออกของฉัน (สำหรับการคูณและการเพิ่ม):

static boolean wouldOverflowOccurwhenMultiplying(int a, int b) {
    // If either a or b are Integer.MIN_VALUE, then multiplying by anything other than 0 or 1 will result in overflow
    if (a == 0 || b == 0) {
        return false;
    } else if (a > 0 && b > 0) { // both positive, non zero
        return a > Integer.MAX_VALUE / b;
    } else if (b < 0 && a < 0) { // both negative, non zero
        return a < Integer.MAX_VALUE / b;
    } else { // exactly one of a,b is negative and one is positive, neither are zero
        if (b > 0) { // this last if statements protects against Integer.MIN_VALUE / -1, which in itself causes overflow.
            return a < Integer.MIN_VALUE / b;
        } else { // a > 0
            return b < Integer.MIN_VALUE / a;
        }
    }
}

boolean wouldOverflowOccurWhenAdding(int a, int b) {
    if (a > 0 && b > 0) {
        return a > Integer.MAX_VALUE - b;
    } else if (a < 0 && b < 0) {
        return a < Integer.MIN_VALUE - b;
    }
    return false;
}

อย่าลังเลที่จะแก้ไขหากผิดหรือง่ายขึ้น ฉันได้ทำการทดสอบด้วยวิธีการคูณซึ่งส่วนใหญ่เป็นคดีเล็ก แต่ก็อาจผิด


การหารนั้นจะช้าเมื่อเทียบกับการคูณ สำหรับint*intฉันคิดว่าเพียงแค่คัดเลือกlongและดูว่าผลลัพธ์ที่เหมาะสมintจะเป็นวิธีที่เร็วที่สุดหรือไม่ สำหรับlong*longถ้าหากตัวถูกดำเนินการเป็นปกติให้ค่าเป็นบวกเราสามารถแบ่งแต่ละส่วนออกเป็นครึ่งบนและส่วนล่าง 32 บิตเลื่อนแต่ละครึ่งเป็นยาว (ระวังเกี่ยวกับส่วนขยายของเครื่องหมาย!) แล้วคำนวณผลิตภัณฑ์สองส่วน (หนึ่งในครึ่งบนควร เป็นศูนย์]
supercat

เมื่อคุณพูดว่า "เป็นเวลานาน * ถ้าตัวถูกดำเนินการเป็นปกติให้เป็นค่าบวก ... " คุณจะทำ normalizing Long.MIN_VALUE อย่างไร
Fragorl

วิธีการเหล่านี้อาจน่าสนใจหากจำเป็นต้องทดสอบว่ามีบางสิ่งที่ล้นก่อนที่จะทำการคำนวณจริงหรือไม่ มันอาจจะดีสำหรับการทดสอบเช่นอินพุตของผู้ใช้ที่ใช้สำหรับการคำนวณเช่นแทนที่จะจับข้อยกเว้นเมื่อมันเกิดขึ้น
Maarten Bodewes

8

มีไลบรารีที่ให้การดำเนินการทางคณิตศาสตร์อย่างปลอดภัยซึ่งจะตรวจสอบจำนวนเต็มล้น / อันเดอร์โฟลว์ ตัวอย่างเช่นIntMath.checkedAddของ Guava (int a, int b)จะส่งคืนผลรวมของaและbหากไม่ได้ล้นและมีการส่งArithmeticExceptionหากa + bล้นในการintคำนวณทางคณิตศาสตร์ที่ลงนามแล้ว


ใช่นั่นเป็นความคิดที่ดีเว้นแต่คุณจะเป็น Java 8 หรือสูงกว่าซึ่งในกรณีนี้Mathคลาสจะมีรหัสที่คล้ายกัน
Maarten Bodewes

6

มันล้อมรอบ

เช่น:

public class Test {

    public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        int j = Integer.MIN_VALUE;

        System.out.println(i+1);
        System.out.println(j-1);
    }
}

พิมพ์

-2147483648
2147483647

ดี! และตอนนี้คุณตอบได้อย่างไร, ตรวจหามันเป็นแคลคูลัสเชิงซ้อนได้อย่างไร?
Aubin

5

ฉันคิดว่าคุณควรใช้สิ่งนี้และเรียกว่าการถ่ายทอดสด:

public int multiplyBy2(int x) throws ArithmeticException {
    long result = 2 * (long) x;    
    if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){
        throw new ArithmeticException("Integer overflow");
    }
    return (int) result;
}

คุณสามารถอ่านเพิ่มเติมได้ที่นี่: ตรวจจับหรือป้องกันการล้นของจำนวนเต็ม

มันเป็นแหล่งที่เชื่อถือได้ค่อนข้าง


3

มันไม่ได้ทำอะไรเลยภายใต้ / ล้นเกิดขึ้น

"-1" ที่เป็นผลลัพธ์ของการคำนวณที่โอเวอร์โฟลไม่แตกต่างจาก "-1" ที่เป็นผลมาจากข้อมูลอื่น ๆ ดังนั้นคุณไม่สามารถบอกผ่านสถานะบางอย่างหรือโดยการตรวจสอบเพียงค่าว่ามันล้น

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


ไม่ใช่สถานการณ์จริง ๆ มีเพียงสิ่งที่ฉันอยากรู้ หากคุณต้องการตัวอย่างการใช้งานกรณีนี้คือหนึ่ง: ฉันมีชั้นเรียนที่มีตัวแปรภายในของตัวเองที่เรียกว่า 'วินาที' ฉันมีสองวิธีซึ่งใช้จำนวนเต็มเป็นพารามิเตอร์และจะเพิ่มขึ้นหรือลดลง (ตามลำดับ) 'วินาที' โดยมาก คุณจะทดสอบหน่วยว่า underflow / overflow เกิดขึ้นอย่างไรและคุณจะป้องกันไม่ให้เกิดขึ้นได้อย่างไร
KushalP

1
static final int safeAdd(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE - right
                : left < Integer.MIN_VALUE - right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left + right;
}

static final int safeSubtract(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left < Integer.MIN_VALUE + right
                : left > Integer.MAX_VALUE + right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left - right;
}

static final int safeMultiply(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE/right
                  || left < Integer.MIN_VALUE/right
                : (right < -1 ? left > Integer.MIN_VALUE/right
                                || left < Integer.MAX_VALUE/right
                              : right == -1
                                && left == Integer.MIN_VALUE) ) {
    throw new ArithmeticException("Integer overflow");
  }
  return left * right;
}

static final int safeDivide(int left, int right)
                 throws ArithmeticException {
  if ((left == Integer.MIN_VALUE) && (right == -1)) {
    throw new ArithmeticException("Integer overflow");
  }
  return left / right;
}

static final int safeNegate(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return -a;
}
static final int safeAbs(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return Math.abs(a);
}

2
นี้จัดการทดสอบ แม้ว่าจะไม่ได้อธิบายว่า Java จัดการ underflow และ overflows จำนวนเต็มได้อย่างไร (เพิ่มข้อความเพื่ออธิบาย)
Spencer Wieczorek


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