Java 8 Unterstützung für nicht signierte Arithmetik

1. Überblick

Seit Anbeginn von Java sind alle numerischen Datentypen signiert. In vielen Situationen müssen jedoch vorzeichenlose Werte verwendet werden. Wenn wir beispielsweise die Anzahl der Vorkommnisse eines Ereignisses zählen, möchten wir keinen negativen Wert feststellen.

Die Unterstützung für vorzeichenlose Arithmetik war schließlich ab Version 8 Bestandteil des JDK. ** Diese Unterstützung erfolgte in Form der unsigned Integer-API, die hauptsächlich statische Methoden in den Klassen Integer und Long enthielt.

In diesem Lernprogramm gehen wir diese API durch und geben Anweisungen zur korrekten Verwendung nicht signierter Nummern

2. Darstellungen auf Bitebene

Um zu verstehen, wie mit vorzeichenbehafteten und vorzeichenlosen Zahlen verfahren wird, betrachten wir zuerst ihre Darstellung auf Bitebene

  • In Java werden Zahlen mit dem Komplement-System der beiden Codes codiert. ** Diese Codierung implementiert viele grundlegende arithmetische Operationen, einschließlich Addition, Subtraktion und Multiplikation, auf gleiche Weise, unabhängig davon, ob die Operanden signiert oder unsigniert sind.

Die Dinge sollten mit einem Codebeispiel klarer sein. Der Einfachheit halber verwenden wir Variablen des Datentyps byte primitive.

Die Operationen sind für andere ganzzahlige numerische Typen ähnlich, z. B. short , int oder long .

Angenommen, wir haben einen Typ byte mit dem Wert von 100 . Diese Nummer hat die Binärdarstellung 0110 0100__.

Wir verdoppeln diesen Wert:

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

Der linke Verschiebungsoperator im angegebenen Code verschiebt alle Bits in der Variablen b1 um eine Position nach links, wodurch der Wert technisch doppelt so groß wird. Die binäre Darstellung der Variablen b2 ist dann 1100 1000__.

In einem System ohne Vorzeichen entspricht dieser Wert einer Dezimalzahl, die 2 ^ 7 2 ^ 6 2 ^ 3 oder 200 entspricht. Trotzdem arbeitet in einem signierten System das am weitesten links stehende Bit als Vorzeichenbit. Das Ergebnis ist daher 2 ^ 7 2 ^ 6 2 ^ 3 oder -56__.

Ein schneller Test kann das Ergebnis verifizieren:

assertEquals(-56, b2);

Wir können sehen, dass die Berechnungen von vorzeichenbehafteten und vorzeichenlosen Zahlen gleich sind. Unterschiede treten nur auf, wenn die JVM eine binäre Darstellung als Dezimalzahl interpretiert.

Die Additions-, Subtraktions- und Multiplikationsoperationen können mit vorzeichenlosen Zahlen arbeiten, ohne dass Änderungen im JDK erforderlich sind. Andere Operationen wie Vergleich oder Division behandeln vorzeichenbehaftete und vorzeichenlose Zahlen unterschiedlich.

Hier kommt die API ohne Vorzeichen ins Spiel.

3. Die vorzeichenlose Integer-API

Die unsigned Integer-API bietet Unterstützung für vorzeichenlose Integer-Arithmetik in Java 8. Die meisten Mitglieder dieser API sind statische Methoden in den Klassen Integer und Long .

Methoden in diesen Klassen funktionieren ähnlich. Wir konzentrieren uns daher nur auf die Integer -Klasse und lassen die Long -Klasse der Kürze halber aus.

3.1. Vergleich

Die Integer -Klasse definiert eine Methode mit dem Namen compareUnsigned zum Vergleich von Nummern ohne Vorzeichen. Diese Methode betrachtet alle binären Werte ohne Vorzeichen und ignoriert den Begriff des Vorzeichenbits.

Beginnen wir mit zwei Zahlen an den Grenzen des Datentyps int :

int positive = Integer.MAX__VALUE;
int negative = Integer.MIN__VALUE;

Wenn wir diese Zahlen als vorzeichenbehaftete Werte vergleichen, ist positive offensichtlich größer als negative :

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

Beim Vergleich von Zahlen als vorzeichenlose Werte wird das ganz linke Bit anstelle des Vorzeichenbits als höchstwertiges Bit betrachtet. Das Ergebnis ist also anders, wobei positive kleiner als negative ist:

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

Es sollte klarer sein, wenn wir die binäre Darstellung dieser Zahlen betrachten:

  • MAX VALUE 0111 1111 …​ 1111

  • MIN VALUE 1000 0000 …​ 0000

Wenn das am weitesten links stehende Bit ein reguläres Wertbit ist, ist MIN VALUE eine Einheit größer als MAX VALUE im Binärsystem. Dieser Test bestätigt:

assertEquals(negative, positive + 1);

3.2. Division und Modulo

Genau wie bei der Vergleichsoperation verarbeiten die vorzeichenlosen Divisions- und Modulooperationen alle Bits als Wertebits. Die Quotienten und Reste unterscheiden sich daher, wenn wir diese Operationen mit vorzeichenbehafteten und vorzeichenlosen Zahlen durchführen:

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

Wenn ein String mit der parseUnsignedInt -Methode analysiert wird, kann das Textargument eine Zahl darstellen, die größer als MAX VALUE __ ist. **

Ein derartiger großer Wert kann nicht mit der parseInt -Methode analysiert werden, die nur die textuelle Darstellung von Zahlen von MIN VALUE bis MAX VALUE verarbeiten kann.

Der folgende Testfall überprüft die Analyseergebnisse:

Throwable thrown = catchThrowable(() -> Integer.parseInt("2147483648"));
assertThat(thrown).isInstanceOf(NumberFormatException.class);

assertEquals(Integer.MAX__VALUE + 1, Integer.parseUnsignedInt("2147483648"));

Beachten Sie, dass die parseUnsignedInt -Methode eine Zeichenfolge parsen kann, die eine größere Zahl als MAX VALUE__ angibt, jedoch keine negative Darstellung parsen kann.

3.4. Formatierung

Ähnlich wie beim Parsen betrachtet eine vorzeichenlose Operation beim Formatieren einer Zahl alle Bits als Wertebits. Folglich können wir die Textdarstellung einer Zahl erzeugen, die etwa doppelt so groß ist wie MAX VALUE __.

Der folgende Testfall bestätigt das Formatierungsergebnis von MIN VALUE__ in beiden Fällen - signiert und nicht signiert:

String signedString = Integer.toString(Integer.MIN__VALUE);
assertEquals("-2147483648", signedString);

String unsignedString = Integer.toUnsignedString(Integer.MIN__VALUE);
assertEquals("2147483648", unsignedString);

4. Vor-und Nachteile

Viele Entwickler, insbesondere diejenigen, die aus einer Sprache stammen, die unsignierte Datentypen unterstützt, wie z. B. C, begrüßen die Einführung von unsignierten Arithmetikoperationen. Dies ist jedoch nicht unbedingt eine gute Sache.

Es gibt zwei Hauptgründe für die Nachfrage nach vorzeichenlosen Nummern.

Erstens gibt es Fälle, in denen ein negativer Wert niemals vorkommen kann, und die Verwendung eines vorzeichenlosen Typs kann einen solchen Wert von vornherein verhindern.

Zweitens können wir bei einem vorzeichenlosen Typ den Bereich der verwendbaren positiven Werte im Vergleich zu seinem signierten Gegenstück verdoppeln.

Analysieren wir die Gründe für den Aufruf von nicht unterzeichneten Zahlen.

  • Wenn eine Variable immer nicht negativ sein sollte, kann ein Wert unter 0 hilfreich sein, um eine Ausnahmesituation anzuzeigen. **

Zum Beispiel gibt die Methode String.indexOf die Position des ersten Vorkommens eines bestimmten Zeichens in einer Zeichenfolge zurück. Der Index -1 kann leicht das Fehlen eines solchen Zeichens anzeigen.

Der andere Grund für vorzeichenlose Zahlen ist die Erweiterung des Wertebereichs. Wenn der Bereich eines signierten Typs jedoch nicht ausreicht, ist es unwahrscheinlich, dass ein doppelter Bereich ausreicht. **

Falls ein Datentyp nicht groß genug ist, müssen Sie einen anderen Datentyp verwenden, der viel größere Werte unterstützt, z. B. long anstelle von int oder BigInteger anstelle von long .

Ein weiteres Problem bei der API ohne Vorzeichen ist, dass die binäre Form einer Zahl unabhängig davon ist, ob sie vorzeichenbehaftet ist oder nicht. Es ist daher einfach, vorzeichenbehaftete und vorzeichenlose Werte zu mischen, was zu unerwarteten Ergebnissen führen kann .

5. Fazit

Die Unterstützung für nicht signierte Arithmetik in Java wurde von vielen Leuten gewünscht. Die Vorteile sind jedoch unklar. Bei der Verwendung dieser neuen Funktion sollten wir vorsichtig vorgehen, um unerwartete Ergebnisse zu vermeiden.

Wie immer ist der Quellcode für diesen Artikel verfügbar: https://github.com/eugenp/tutorials/tree/master/core-java-8 Über GitHub.