Math.abs mengembalikan nilai yang salah untuk Integer.Min_VALUE

90

Kode ini:

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

Kembali -2147483648

Haruskah itu tidak mengembalikan nilai absolut sebagai 2147483648?

pengguna665319
sumber

Jawaban:

102

Integer.MIN_VALUEadalah -2147483648, tetapi nilai tertinggi yang dapat dimiliki oleh integer 32 bit adalah +2147483647. Mencoba untuk mewakili +2147483648dalam int 32 bit akan secara efektif "berguling" ke -2147483648. Ini karena, saat menggunakan bilangan bulat bertanda, kedua representasi biner pelengkap dari +2147483648dan -2147483648adalah identik. Ini bukan masalah, bagaimanapun, karena +2147483648dianggap di luar jangkauan.

Untuk sedikit lebih banyak membaca tentang masalah ini, Anda mungkin ingin melihat artikel Wikipedia tentang pelengkap Two .

jonmorgan
sumber
6
Yah, bukan masalah meremehkan dampaknya, itu bisa berarti masalah. Secara pribadi saya lebih suka memiliki pengecualian atau sistem angka yang tumbuh secara dinamis dalam bahasa tingkat yang lebih tinggi.
Maarten Bodewes
40

Perilaku yang Anda tunjukkan memang kontra-intuitif. Namun, perilaku ini adalah perilaku yang ditentukan oleh javadoc untukMath.abs(int) :

Jika argumen tidak negatif, argumen dikembalikan. Jika argumennya negatif, negasi argumen dikembalikan.

Artinya, Math.abs(int)harus berperilaku seperti kode Java berikut:

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

Artinya, dalam kasus negatif -x,.

Menurut JLS bagian 15.15.4 , -xadalah sama dengan (~x)+1, di mana ~operator pelengkap bitwise.

Untuk memeriksa apakah ini terdengar benar, mari kita ambil -1 sebagai contoh.

Nilai integer -1dapat dicatat seperti 0xFFFFFFFFdalam heksadesimal di Java (lihat ini dengan a printlnatau metode lain). Mengambil -(-1)demikian memberi:

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

Jadi, itu berhasil.

Mari kita coba sekarang dengan Integer.MIN_VALUE. Mengetahui bahwa bilangan bulat terendah dapat diwakili oleh 0x80000000, yaitu, bit pertama disetel ke 1 dan 31 bit tersisa disetel ke 0, kami memiliki:

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

Dan inilah mengapa Math.abs(Integer.MIN_VALUE)kembali Integer.MIN_VALUE. Juga mencatat bahwa 0x7FFFFFFFadalah Integer.MAX_VALUE.

Karena itu, bagaimana kita dapat menghindari masalah karena nilai pengembalian kontra-intuitif ini di masa mendatang?

  • Kita bisa, seperti yang keluar menunjuk oleh @Bombe , cor kami ints untuk longsebelumnya. Kami, bagaimanapun, harus baik

    • membuangnya kembali ke ints, yang tidak berfungsi karena Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Atau lanjutkan dengan longberharap bahwa kita tidak akan pernah menelepon Math.abs(long)dengan nilai yang sama Long.MIN_VALUE, karena kita juga punya Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Kita bisa menggunakan BigIntegers dimana saja, karena BigInteger.abs()memang selalu mengembalikan nilai positif. Ini adalah alternatif yang baik, meskipun sedikit lebih lambat daripada memanipulasi tipe integer mentah.

  • Kita dapat menulis pembungkus kita sendiri untuk Math.abs(int), seperti ini:

/**
 * 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);
}
  • Gunakan bilangan bulat bitwise AND untuk menghapus bit tinggi, pastikan hasilnya tidak negatif: int positive = value & Integer.MAX_VALUE(pada dasarnya meluap dari Integer.MAX_VALUEke 0alih-alih Integer.MIN_VALUE)

Sebagai catatan terakhir, masalah ini sepertinya sudah diketahui beberapa waktu lalu. Lihat misalnya entri ini tentang aturan findbugs yang sesuai .

bernard paulus
sumber
12

Inilah yang dikatakan oleh dokumen Java untuk Math.abs () di javadoc :

Perhatikan bahwa jika argumen sama dengan nilai Integer.MIN_VALUE, nilai int terwakili yang paling negatif, hasilnya adalah nilai yang sama, yaitu negatif.

moe
sumber
4

Untuk melihat hasil yang Anda harapkan, transmisikan Integer.MIN_VALUEke long:

System.out.println(Math.abs((long) Integer.MIN_VALUE));
Bombe
sumber
1
Perbaikan yang mungkin, memang! Namun, ini tidak menyelesaikan fakta yang Math.absberlawanan dengan intuisi dengan mengembalikan angka negatif:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard paulus
1
@bernardpaulus, nah, apa yang harus dilakukan selain melempar ArithmeticException? Selain itu, perilaku tersebut didokumentasikan dengan jelas dalam dokumentasi API.
Bombe
tidak ada jawaban yang bagus untuk pertanyaan Anda ... Saya hanya ingin menunjukkan bahwa perilaku ini, yang merupakan sumber bug, tidak diperbaiki dengan penggunaan Math.abs(long). Saya minta maaf atas kesalahan saya di sini: Saya mengira Anda mengusulkan penggunaan Math.abs(long)sebagai perbaikan, ketika Anda menunjukkannya sebagai cara sederhana untuk "melihat hasil yang diharapkan penanya". Maaf.
bernard paulus
Di Java 15 dengan metode baru sebenarnya Exception dilemparkan.
chiperortiz
1

2147483648 tidak dapat disimpan dalam integer di java, representasi binernya sama dengan -2147483648.

ymajoros
sumber
0

Tetapi (int) 2147483648L == -2147483648 ada satu bilangan negatif yang tidak memiliki padanan positif sehingga tidak ada nilai positif untuk itu. Anda akan melihat perilaku yang sama dengan Long.MAX_VALUE.

Peter Lawrey
sumber
0

Ada perbaikan ini di Jawa 15 akan menjadi metode int dan panjang. Mereka akan hadir di kelas

java.lang.Math and java.lang.StrictMath

Metodenya.

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

Jika Anda lulus

Integer.MIN_VALUE

ATAU

Long.MIN_VALUE

Pengecualian dilemparkan.

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

Saya ingin melihat apakah Long.MIN_VALUE atau Integer.MIN_VALUE dilewatkan, nilai positif akan dikembalikan dan bukan pengecualian tetapi.

chiperortiz
sumber
-1

Math.abs tidak bekerja sepanjang waktu dengan angka-angka besar. Saya menggunakan logika kode kecil ini yang saya pelajari ketika saya berusia 7 tahun!

if(Num < 0){
  Num = -(Num);
} 
Dave
sumber
Ada apa sdisini?
aioobe
Maaf saya lupa memperbaruinya dari kode asli saya
Dave
Jadi, apa hasil ini jika Numsama dengan Integer.MIN_VALUEsebelum cuplikan?
aioobe