Math.abs trả về giá trị sai cho Integer.Min_VALUE


90

Mã này:

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

Lợi nhuận -2147483648

Nó không nên trả về giá trị tuyệt đối là 2147483648?

Câu trả lời:


102

Integer.MIN_VALUE-2147483648, nhưng giá trị cao nhất mà một số nguyên 32 bit có thể chứa là +2147483647. Cố gắng biểu diễn +2147483648trong 32 bit int sẽ có hiệu quả "cuộn qua" đến -2147483648. Điều này là do, khi sử dụng số nguyên có dấu, hai biểu diễn nhị phân bổ sung của +2147483648-2147483648là giống hệt nhau. Đây không phải là một vấn đề, tuy nhiên, +2147483648được coi là nằm ngoài phạm vi.

Để đọc thêm một chút về vấn đề này, bạn có thể muốn xem bài viết trên Wikipedia về phần bổ sung của Two .


6
Chà, không phải vấn đề là đánh giá thấp tác động, nó rất có thể có nghĩa là có vấn đề. Cá nhân tôi muốn có một ngoại lệ hoặc một hệ thống số phát triển linh hoạt trong một ngôn ngữ cấp cao hơn.
Maarten Bodewes 19/12/12

40

Hành vi bạn chỉ ra thực sự là phản trực giác. Tuy nhiên, hành vi này là hành vi được javadocMath.abs(int) chỉ định cho :

Nếu đối số không phủ định, đối số được trả về. Nếu đối số là phủ định, thì phủ định của đối số được trả về.

Đó là, Math.abs(int)sẽ hoạt động giống như mã Java sau:

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

Đó là, trong trường hợp tiêu cực -x,.

Theo JLS phần 15.15.4 , -xbằng (~x)+1, ở đâu ~là toán tử bổ sung bit.

Để kiểm tra xem điều này có đúng không, hãy lấy -1 làm ví dụ.

Giá trị số nguyên -1có thể được ghi chú như 0xFFFFFFFFtrong hệ thập lục phân trong Java (hãy kiểm tra điều này bằng a printlnhoặc bất kỳ phương thức nào khác). Lấy -(-1)do đó cung cấp cho:

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

Vì vậy, nó hoạt động.

Hãy để chúng tôi thử ngay bây giờ với Integer.MIN_VALUE. Biết rằng số nguyên thấp nhất có thể được biểu diễn bằng 0x80000000, nghĩa là, bit đầu tiên được đặt thành 1 và 31 bit còn lại được đặt thành 0, chúng ta có:

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

Và đây là lý do tại sao Math.abs(Integer.MIN_VALUE)trả về Integer.MIN_VALUE. Cũng lưu ý rằng 0x7FFFFFFFInteger.MAX_VALUE.

Điều đó nói rằng, làm thế nào chúng ta có thể tránh các vấn đề do giá trị trả về phản trực giác này trong tương lai?

  • Chúng tôi có thể, như ra nhọn bởi @Bombe , đúc của chúng tôi ints đến longtrước. Tuy nhiên, chúng ta phải

    • chuyển chúng trở lại thành ints, không hoạt động bởi vì Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Hoặc tiếp tục với longs bằng cách nào đó hy vọng rằng chúng ta sẽ không bao giờ gọi Math.abs(long)với giá trị bằng Long.MIN_VALUE, vì chúng ta cũng có Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Chúng ta có thể sử dụng BigIntegers ở mọi nơi, vì BigInteger.abs()nó thực sự luôn trả về một giá trị dương. Đây là một giải pháp thay thế tốt, mặc dù chậm hơn một chút so với thao tác với các kiểu số nguyên thô.

  • Chúng ta có thể viết trình bao bọc của riêng mình Math.abs(int), như thế này:

/**
 * 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);
}
  • Sử dụng một số nguyên theo chiều dọc bit AND để xóa bit cao, đảm bảo rằng kết quả là không âm: int positive = value & Integer.MAX_VALUE(về cơ bản tràn từ Integer.MAX_VALUEsang 0thay vì Integer.MIN_VALUE)

Lưu ý cuối cùng, vấn đề này dường như đã được biết đến trong một thời gian. Xem ví dụ mục nhập này về quy tắc findbugs tương ứng .


12

Đây là những gì Java doc nói cho Math.abs () trong javadoc :

Lưu ý rằng nếu đối số bằng với giá trị của Integer.MIN_VALUE, giá trị int có thể biểu diễn âm nhất, thì kết quả sẽ là giá trị tương tự, giá trị này là số âm.


4

Để xem kết quả mà bạn mong đợi, hãy truyền Integer.MIN_VALUEtới long:

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

1
Có thể sửa chữa, thực sự! Tuy nhiên, điều này không giải quyết thực tế là Math.absđang được phản trực giác bằng cách trả lại một số âm:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard Paulus

1
@bernardpaulus, chà, nó phải làm gì, ngoài việc ném một ArithmeticExceptioncái? Ngoài ra, hành vi được ghi lại rõ ràng trong tài liệu API.
Bombe

không có câu trả lời tốt cho câu hỏi của bạn ... Tôi chỉ muốn chỉ ra rằng hành vi này, là một nguồn lỗi, không được khắc phục bằng cách sử dụng Math.abs(long). Tôi xin lỗi vì sai lầm của tôi ở đây: Tôi đã nghĩ rằng bạn đề xuất sử dụng Math.abs(long)như một bản sửa lỗi, khi bạn cho thấy nó như một cách đơn giản để "xem kết quả mà người hỏi đang mong đợi". Lấy làm tiếc.
bernard paulus

Trên thực tế, trong Java 15 với các phương thức mới, một Ngoại lệ được ném ra.
chiperortiz

1

2147483648 không thể được lưu trữ dưới dạng số nguyên trong java, biểu diễn nhị phân của nó giống như -2147483648.


0

Nhưng (int) 2147483648L == -2147483648 có một số âm không có giá trị dương tương đương nên không có giá trị dương cho nó. Bạn sẽ thấy hành vi tương tự với Long.MAX_VALUE.


0

Có một bản sửa lỗi cho điều này trong Java 15 sẽ là một phương thức để int và long. Họ sẽ có mặt trên các lớp học

java.lang.Math and java.lang.StrictMath

Các phương pháp.

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

Nếu bạn vượt qua

Integer.MIN_VALUE

HOẶC LÀ

Long.MIN_VALUE

Một Exception được ném ra.

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

Tôi muốn xem nếu Long.MIN_VALUE hoặc Integer.MIN_VALUE được chuyển một giá trị dương sẽ được trả về và không phải là một ngoại lệ.


-1

Math.abs không hoạt động mọi lúc với những con số lớn. Tôi sử dụng logic mã nhỏ này mà tôi đã học được khi tôi 7 tuổi!

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

Cái gì sở đây?
aioobe

Xin lỗi, tôi đã quên cập nhật điều đó từ mã gốc của mình
Dave

Vậy điều này dẫn đến kết quả gì nếu Numbằng Integer.MIN_VALUEtrước đoạn mã?
aioobe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.