Math.abs ส่งคืนค่าที่ไม่ถูกต้องสำหรับ Integer.Min_VALUE


คำตอบ:


103

Integer.MIN_VALUEเป็น-2147483648แต่มูลค่าสูงสุด 32 +2147483647บิตจำนวนเต็มสามารถมีคือ ความพยายามที่จะเป็นตัวแทน+2147483648ใน int 32 บิตจะได้อย่างมีประสิทธิภาพ "เกลือกกลิ้ง" -2147483648เพื่อ นี้เป็นเพราะเมื่อใช้ลงนามจำนวนเต็มสองของเสริมของเลขฐานสอง+2147483648และ-2147483648เหมือนกัน อย่างไรก็ตามนี่ไม่ใช่ปัญหา แต่+2147483648ถือว่าอยู่นอกขอบเขต

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


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

41

พฤติกรรมที่คุณชี้ให้เห็นนั้นขัดแย้งกับธรรมชาติ อย่างไรก็ตามพฤติกรรมนี้เป็นลักษณะที่ระบุโดยjavadoc สำหรับMath.abs(int) :

ถ้าอาร์กิวเมนต์ไม่เป็นลบอาร์กิวเมนต์จะถูกส่งกลับ ถ้าอาร์กิวเมนต์เป็นลบการปฏิเสธของอาร์กิวเมนต์จะถูกส่งกลับ

นั่นคือMath.abs(int)ควรทำงานเหมือนโค้ด Java ต่อไปนี้:

public static int abs(int x){
    if (x >= 0) {
        return x;
    }
    return -x;
}

-xนั่นคือในกรณีที่เชิงลบ

ตามที่ส่วน JLS 15.15.4ที่-xเท่ากับ(~x)+1ที่~เป็นผู้ประกอบการระดับบิตสมบูรณ์

หากต้องการตรวจสอบว่าเสียงถูกต้องหรือไม่ลองใช้ -1 เป็นตัวอย่าง

ค่าจำนวนเต็ม-1สามารถระบุได้เป็น0xFFFFFFFFเลขฐานสิบหกใน Java (ตรวจสอบสิ่งนี้ด้วย a printlnหรือวิธีการอื่น ๆ ) การดำเนินการดังกล่าว-(-1)จะช่วยให้:

-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1

ดังนั้นมันได้ผล

Integer.MIN_VALUEให้เราพยายามที่ตอนนี้มี เมื่อทราบว่าจำนวนเต็มต่ำสุดสามารถแสดงได้0x80000000นั่นคือบิตแรกที่ตั้งค่าเป็น 1 และ 31 บิตที่เหลือตั้งค่าเป็น 0 เรามี:

-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1 
                     = 0x80000000 = Integer.MIN_VALUE

และนี่คือเหตุผลที่Math.abs(Integer.MIN_VALUE)กลับInteger.MIN_VALUEมา นอกจากนี้ทราบว่าเป็น0x7FFFFFFFInteger.MAX_VALUE

ที่กล่าวว่าเราจะหลีกเลี่ยงปัญหาอันเนื่องมาจากมูลค่าผลตอบแทนที่ตอบโต้ได้ง่ายในอนาคตได้อย่างไร

  • เราสามารถ, เป็นแหลมออกโดย @Bombeหล่อของเราintที่จะlongก่อน อย่างไรก็ตามเราต้องอย่างใดอย่างหนึ่ง

    • โยนพวกเขากลับเข้าints Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)ซึ่งไม่ได้เพราะ
    • หรือดำเนินการกับlongs อย่างใดหวังว่าเราจะไม่เรียกร้องMath.abs(long)ที่มีค่าเท่ากับตั้งแต่เรายังมีLong.MIN_VALUEMath.abs(Long.MIN_VALUE) == Long.MIN_VALUE
  • เราสามารถใช้BigIntegers ได้ทุกที่เพราะBigInteger.abs()จะคืนค่าเป็นบวกเสมอ นี่เป็นทางเลือกที่ดีแม้ว่าจะช้ากว่าการจัดการประเภทจำนวนเต็มดิบเล็กน้อย

  • เราสามารถเขียนกระดาษห่อของเราเองได้Math.abs(int)ดังนี้:

/**
 * Fail-fast wrapper for {@link Math#abs(int)}
 * @param x
 * @return the absolute value of x
 * @throws ArithmeticException when a negative value would have been returned by {@link Math#abs(int)}
 */
public static int abs(int x) throws ArithmeticException {
    if (x == Integer.MIN_VALUE) {
        // fail instead of returning Integer.MAX_VALUE
        // to prevent the occurrence of incorrect results in later computations
        throw new ArithmeticException("Math.abs(Integer.MIN_VALUE)");
    }
    return Math.abs(x);
}
  • ใช้จำนวนเต็มบิตและเพื่อล้างบิตสูงเพื่อให้แน่ใจว่าผลลัพธ์ไม่เป็นลบ: int positive = value & Integer.MAX_VALUE(โดยพื้นฐานแล้วจะล้นจากInteger.MAX_VALUEถึง0แทนที่จะเป็นInteger.MIN_VALUE)

ในท้ายที่สุดปัญหานี้ดูเหมือนจะเป็นที่ทราบกันดีอยู่แล้ว ดูตัวอย่างรายการนี้เกี่ยวกับกฎ findbugs ที่เกี่ยวข้อง


12

นี่คือสิ่งที่ Java doc พูดสำหรับ Math.abs () ในjavadoc :

โปรดสังเกตว่าถ้าอาร์กิวเมนต์เท่ากับค่า Integer.MIN_VALUE ซึ่งเป็นค่า int ที่เป็นค่าลบมากที่สุดผลลัพธ์จะเป็นค่าเดียวกันซึ่งเป็นค่าลบ


4

หากต้องการดูผลลัพธ์ที่คุณคาดหวังให้ส่งInteger.MIN_VALUEไปที่long:

System.out.println(Math.abs((long) Integer.MIN_VALUE));

1
การแก้ไขที่เป็นไปได้แน่นอน! อย่างไรก็ตามสิ่งนี้ไม่ได้แก้ปัญหาความจริงที่Math.absถูกต่อต้านโดยการคืนค่าจำนวนลบ:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard paulus

1
@bernardpaulus เอาละมันควรจะทำอะไรนอกจากโยนArithmeticException? นอกจากนี้พฤติกรรมนี้ยังได้รับการบันทึกไว้อย่างชัดเจนในเอกสาร API
Bombe

ไม่มีคำตอบที่ดีสำหรับคำถามของคุณ ... Math.abs(long)ฉันแค่อยากจะชี้ให้เห็นว่าพฤติกรรมนี้ซึ่งเป็นแหล่งที่มาของข้อบกพร่องที่ไม่ได้รับการแก้ไขโดยการใช้งานของ ขออภัยในความผิดพลาดของฉันที่นี่: ฉันคิดว่าคุณเสนอให้ใช้Math.abs(long)เพื่อแก้ไขเมื่อคุณแสดงให้เห็นว่าเป็นวิธีง่ายๆในการ "เห็นผลลัพธ์ที่ผู้ถามคาดหวัง" ขออภัย.
bernard paulus

ใน Java 15 ด้วยวิธีการใหม่ในความเป็นจริงมีการยกเว้น Exception
chiperortiz

1

2147483648 ไม่สามารถเก็บเป็นจำนวนเต็มใน java ได้การแสดงไบนารีจะเหมือนกับ -2147483648


0

แต่(int) 2147483648L == -2147483648 มีจำนวนลบหนึ่งตัวที่ไม่มีค่าเทียบเท่าบวกดังนั้นจึงไม่มีค่าบวกสำหรับมัน คุณจะเห็นพฤติกรรมเดียวกันกับ Long.MAX_VALUE


0

มีการแก้ไขนี้ใน Java 15 จะเป็นวิธีการ int และยาว พวกเขาจะนำเสนอในชั้นเรียน

java.lang.Math and java.lang.StrictMath

วิธีการ

public static int absExact(int a)
public static long absExact(long a)

ถ้าคุณผ่าน

Integer.MIN_VALUE

หรือ

Long.MIN_VALUE

ข้อยกเว้นถูกโยนทิ้ง

https://bugs.openjdk.java.net/browse/JDK-8241805

ฉันต้องการดูว่า Long.MIN_VALUE หรือ Integer.MIN_VALUE ถูกส่งผ่านค่าบวกจะได้รับผลตอบแทนไม่ใช่ข้อยกเว้น แต่


-1

Math.abs ใช้งานไม่ได้ตลอดเวลากับตัวเลขจำนวนมากฉันใช้ตรรกะรหัสเล็ก ๆ นี้ที่ฉันเรียนรู้เมื่อฉันอายุ 7 ขวบ!

if(Num < 0){
  Num = -(Num);
} 

ที่sนี่คืออะไร?
aioobe

ขออภัยฉันลืมอัปเดตจากรหัสเดิมของฉัน
Dave

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