Dukungan Aritmatika Tanpa Tanda Tangan Java 8

1. Ikhtisar

Sejak awal Java, semua tipe data numerik ditandatangani. Dalam banyak situasi, bagaimanapun, itu diperlukan untuk menggunakan nilai yang tidak bertanda tangan. Misalnya, jika kita menghitung jumlah kemunculan suatu peristiwa, kita tidak ingin menemukan nilai negatif.

Dukungan untuk aritmatika unsigned akhirnya menjadi bagian dari JDK sejak versi 8. Dukungan ini datang dalam bentuk Unsigned Integer API, yang utamanya berisi metode statis dalam kelas Integer dan Long .

Dalam tutorial ini, kita akan membahas API ini dan memberikan instruksi tentang cara menggunakan nomor tak bertanda tangan dengan benar.

2. Representasi Bit-Level

Untuk memahami cara menangani nomor yang ditandatangani dan tidak, mari kita lihat representasi mereka di tingkat bit terlebih dahulu.

Di Jawa, angka dikodekan menggunakan sistem komplemen keduanya. Pengkodean ini mengimplementasikan banyak operasi aritmatika dasar, termasuk penjumlahan, pengurangan, dan perkalian, dengan cara yang sama, baik operand ditandatangani atau tidak.

Segalanya harus lebih jelas dengan contoh kode. Demi kesederhanaan, kita akan menggunakan variabel dari tipe data primitif byte . Operasi serupa untuk tipe numerik integral lainnya, seperti pendek , int , atau panjang .

Asumsikan kita memiliki beberapa tipe byte dengan nilai 100 . Angka ini memiliki representasi biner 0110_0100 .

Mari gandakan nilai ini:

byte b1 = 100; byte b2 = (byte) (b1 << 1);

Operator shift kiri dalam kode yang diberikan memindahkan semua bit dalam variabel b1 ke posisi kiri, secara teknis membuat nilainya dua kali lebih besar. Representasi biner dari variabel b2 kemudian akan menjadi 1100_1000 .

Dalam sistem tipe unsigned, nilai ini mewakili angka desimal yang setara dengan 2 ^ 7 + 2 ^ 6 + 2 ^ 3 , atau 200 . Namun demikian, dalam sistem bertanda, bit paling kiri berfungsi sebagai bit tanda. Oleh karena itu, hasilnya adalah -2 ^ 7 + 2 ^ 6 + 2 ^ 3 , atau -56 .

Tes cepat dapat memverifikasi hasilnya:

assertEquals(-56, b2);

Kita dapat melihat bahwa perhitungan bilangan bertanda dan tidak bertanda sama. Perbedaan hanya muncul ketika JVM mengartikan representasi biner sebagai angka desimal.

Operasi penjumlahan, pengurangan, dan perkalian dapat bekerja dengan bilangan tak bertanda tangan tanpa memerlukan perubahan apa pun di JDK. Operasi lain, seperti perbandingan atau pembagian, menangani nomor yang ditandatangani dan tidak bertanda tangan secara berbeda.

Di sinilah Unsigned Integer API berperan.

3. API Integer Unsigned

Unsigned Integer API menyediakan dukungan untuk unsigned integer aritmatika di Java 8. Sebagian besar anggota API ini adalah metode statis di kelas Integer dan Long .

Metode di kelas ini bekerja dengan cara yang sama. Karena itu kami akan fokus pada kelas Integer saja, meninggalkan kelas Long untuk singkatnya.

3.1. Perbandingan

The Integer kelas mendefinisikan sebuah metode bernama compareUnsigned untuk membandingkan angka unsigned. Metode ini menganggap semua nilai biner unsigned, mengabaikan pengertian bit tanda.

Mari kita mulai dengan dua angka di batas tipe data int :

int positive = Integer.MAX_VALUE; int negative = Integer.MIN_VALUE;

Jika kita membandingkan angka-angka ini sebagai nilai yang ditandatangani, positif jelas lebih besar dari negatif :

int signedComparison = Integer.compare(positive, negative); assertEquals(1, signedComparison);

Saat membandingkan angka sebagai nilai unsigned, bit paling kiri dianggap sebagai bit paling signifikan daripada bit tanda. Jadi, hasilnya berbeda, dengan positif lebih kecil dari negatif :

int unsignedComparison = Integer.compareUnsigned(positive, negative); assertEquals(-1, unsignedComparison);

Seharusnya lebih jelas jika kita melihat representasi biner dari angka-angka itu:

  • MAX_VALUE -> 0111_1111_… _1111
  • MIN_VALUE -> 1.000_0000_… _0000

Jika bit paling kiri adalah bit nilai reguler, MIN_VALUE adalah satu unit yang lebih besar dari MAX_VALUE dalam sistem biner. Tes ini menegaskan bahwa:

assertEquals(negative, positive + 1);

3.2. Divisi dan Modulo

Sama seperti operasi perbandingan, operasi unsigned division dan modulo memproses semua bit sebagai bit nilai. Oleh karena itu, quotients dan sisanya berbeda ketika kami melakukan operasi ini pada nomor yang ditandatangani dan tidak bertanda tangan:

int positive = Integer.MAX_VALUE; int negative = Integer.MIN_VALUE; assertEquals(-1, negative / positive); assertEquals(1, Integer.divideUnsigned(negative, positive)); assertEquals(-1, negative % positive); assertEquals(1, Integer.remainderUnsigned(negative, positive));

3.3. Parsing

Saat mengurai String menggunakan metode parseUnsignedInt , argumen teks dapat mewakili angka yang lebih besar dari MAX_VALUE .

Nilai besar seperti itu tidak dapat diurai dengan metode parseInt , yang hanya dapat menangani representasi tekstual angka dari MIN_VALUE hingga MAX_VALUE .

Kasus uji berikut memverifikasi hasil penguraian:

Throwable thrown = catchThrowable(() -> Integer.parseInt("2147483648")); assertThat(thrown).isInstanceOf(NumberFormatException.class); assertEquals(Integer.MAX_VALUE + 1, Integer.parseUnsignedInt("2147483648"));

Perhatikan bahwa metode parseUnsignedInt dapat mengurai string yang menunjukkan angka lebih besar dari MAX_VALUE , tetapi akan gagal untuk mengurai representasi negatif apa pun.

3.4. Pemformatan

Mirip dengan parsing, saat memformat angka, operasi unsigned menganggap semua bit sebagai bit nilai. Akibatnya, kami dapat menghasilkan representasi tekstual dari sebuah angka yang kira-kira dua kali lebih besar dari MAX_VALUE .

Kasus uji berikut mengonfirmasi hasil pemformatan MIN_VALUE dalam kedua kasus - bertanda tangan dan tak bertanda tangan:

String signedString = Integer.toString(Integer.MIN_VALUE); assertEquals("-2147483648", signedString); String unsignedString = Integer.toUnsignedString(Integer.MIN_VALUE); assertEquals("2147483648", unsignedString);

4. Pro dan Kontra

Banyak developer, terutama yang berasal dari bahasa yang mendukung tipe data unsigned, seperti C, menyambut baik pengenalan operasi aritmatika unsigned. Namun, ini belum tentu bagus.

There are two main reasons for the demand for unsigned numbers.

First, there are cases for which a negative value can never occur, and using an unsigned type can prevent such a value in the first place. Second, with an unsigned type, we can double the range of usable positive values compared to its signed counterpart.

Let's analyze the rationale behind the appeal for unsigned numbers.

When a variable should always be non-negative, a value less than 0 may be handy in indicating an exceptional situation.

For instance, the String.indexOf method returns the position of the first occurrence of a certain character in a string. The index -1 can easily denote the absence of such a character.

The other reason for unsigned numbers is the expansion of the value space. However, if the range of a signed type isn't enough, it's unlikely that a doubled range would suffice.

In case a data type isn't large enough, we need to use another data type that supports much larger values, such as using long instead of int, or BigInteger rather than long.

Another problem with the Unsigned Integer API is that the binary form of a number is the same regardless of whether it's signed or unsigned. It's therefore easy to mix signed and unsigned values, which may lead to unexpected results.

5. Conclusion

Dukungan untuk aritmatika unsigned di Java telah datang atas permintaan banyak orang. Namun, manfaatnya masih belum jelas. Kami harus berhati-hati saat menggunakan fitur baru ini untuk menghindari hasil yang tidak diharapkan.

Seperti biasa, kode sumber untuk artikel ini tersedia di GitHub.