Java 8 Sokongan Aritmetik Tidak Terancang

1. Gambaran keseluruhan

Sejak awal Java, semua jenis data berangka ditandatangani. Dalam banyak situasi, bagaimanapun, diharuskan menggunakan nilai yang tidak ditandatangani. Sebagai contoh, jika kita menghitung jumlah kejadian suatu peristiwa, kita tidak ingin menemui nilai negatif.

Sokongan untuk aritmetik yang tidak ditandatangani akhirnya menjadi sebahagian dari JDK pada versi 8. Sokongan ini datang dalam bentuk Unegigned Integer API, yang terutama mengandungi kaedah statik di kelas Integer dan Long .

Dalam tutorial ini, kita akan membahas API ini dan memberi arahan mengenai cara menggunakan nombor yang tidak ditandatangani dengan betul.

2. Perwakilan Tahap Bit

Untuk memahami bagaimana menangani nombor yang ditandatangani dan tidak ditandatangani, mari kita perhatikan perwakilannya pada tahap bit terlebih dahulu.

Di Jawa, nombor dikodkan menggunakan sistem pelengkap kedua. Pengekodan ini melaksanakan banyak operasi aritmetik asas, termasuk penambahan, pengurangan, dan pendaraban, dengan cara yang sama, sama ada operan ditandatangani atau tidak ditandatangani.

Perkara harus lebih jelas dengan contoh kod. Demi kesederhanaan, kami akan menggunakan pemboleh ubah jenis data primitif bait . Operasi serupa untuk jenis numerik integral lain, seperti pendek , int , atau panjang .

Anggaplah kita mempunyai beberapa jenis bait dengan nilai 100 . Nombor ini mempunyai perwakilan binari 0110_0100 .

Mari gandakan nilai ini:

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

Operator peralihan kiri dalam kod yang diberikan menggerakkan semua bit dalam pemboleh ubah b1 kedudukan ke kiri, secara teknikal menjadikan nilainya dua kali lebih besar. Perwakilan binari pemboleh ubah b2 akan menjadi 1100_1000 .

Dalam sistem jenis yang tidak ditandatangani, nilai ini mewakili nombor perpuluhan bersamaan dengan 2 ^ 7 + 2 ^ 6 + 2 ^ 3 , atau 200 . Walaupun begitu, dalam sistem yang ditandatangani, bit paling kiri berfungsi sebagai bit tanda. Oleh itu, hasilnya adalah -2 ^ 7 + 2 ^ 6 + 2 ^ 3 , atau -56 .

Ujian pantas dapat mengesahkan hasilnya:

assertEquals(-56, b2);

Kita dapat melihat bahawa pengiraan nombor yang ditandatangani dan yang tidak ditandatangani adalah sama. Perbezaan hanya muncul apabila JVM menafsirkan perwakilan binari sebagai nombor perpuluhan.

Operasi penambahan, pengurangan, dan pendaraban dapat berfungsi dengan nombor yang tidak ditandatangani tanpa memerlukan perubahan pada JDK. Operasi lain, seperti perbandingan atau pembahagian, menangani nombor yang ditandatangani dan tidak ditandatangani secara berbeza.

Di sinilah Unegigned Integer API dimainkan.

3. API Integer Tidak Bertanda

The Unsigned Integer API memberikan sokongan untuk aritmetik integer yang tidak ditandatangani di Java 8. Sebilangan besar anggota API ini adalah kaedah statik di kelas Integer dan Long .

Kaedah dalam kelas ini berfungsi sama. Oleh itu, kita akan fokus pada kelas Integer sahaja, meninggalkan kelas Long kerana singkat.

3.1. Perbandingan

The Integer kelas mentakrifkan kaedah yang dinamakan compareUnsigned untuk membandingkan nombor tak bertanda. Kaedah ini menganggap semua nilai binari tidak ditandatangani, mengabaikan tanggapan bit tanda.

Mari kita mulakan dengan dua nombor di sempadan jenis data int :

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

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

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

Semasa membandingkan nombor sebagai nilai yang tidak ditandatangani, bit paling kiri dianggap sebagai bit yang paling signifikan dan bukan bit tanda. Oleh itu, hasilnya berbeza, dengan positif lebih kecil daripada negatif :

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

Harus lebih jelas jika kita melihat perwakilan binari nombor tersebut:

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

Apabila bit paling kiri adalah bit nilai biasa, MIN_VALUE adalah satu unit lebih besar daripada MAX_VALUE dalam sistem binari. Ujian ini mengesahkan bahawa:

assertEquals(negative, positive + 1);

3.2. Bahagian dan Modulo

Sama seperti operasi perbandingan, operasi bahagian dan modulo yang tidak ditandatangani memproses semua bit sebagai bit nilai. Oleh itu, rundingan dan baki berbeza ketika kami melakukan operasi ini pada nombor yang ditandatangani dan tidak ditandatangani:

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. Menghuraikan

Semasa menguraikan String menggunakan kaedah parseUnsignedInt , argumen teks dapat mewakili angka yang lebih besar daripada MAX_VALUE .

Nilai besar seperti itu tidak dapat dihuraikan dengan kaedah parseInt , yang hanya dapat menangani representasi teks dari angka dari MIN_VALUE hingga MAX_VALUE .

Kes ujian berikut mengesahkan hasil penghuraian:

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

Notice that the parseUnsignedInt method can parse a string indicating a number larger than MAX_VALUE, but will fail to parse any negative representation.

3.4. Formatting

Similar to parsing, when formatting a number, an unsigned operation regards all bits as value bits. Consequently, we can produce the textual representation of a number about twice as large as MAX_VALUE.

The following test case confirms the formatting result of MIN_VALUE in both cases — signed and unsigned:

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

4. Pros and Cons

Many developers, especially those coming from a language that supports unsigned data types, such as C, welcome the introduction of unsigned arithmetic operations. However, this isn't necessarily a good thing.

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

Sokongan untuk aritmetik yang tidak ditandatangani di Jawa datang atas permintaan banyak orang. Walau bagaimanapun, faedah yang dibawanya tidak jelas. Kita harus berhati-hati ketika menggunakan fitur baru ini untuk mengelakkan hasil yang tidak dijangka.

Seperti biasa, kod sumber untuk artikel ini terdapat di GitHub.