Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 21 Bits und Bytes, Mathematisches und Geld
Pfeil 21.1 Bits und Bytes
Pfeil 21.1.1 Die Bit-Operatoren Komplement, Und, Oder und XOR
Pfeil 21.1.2 Repräsentation ganzer Zahlen in Java – das Zweierkomplement
Pfeil 21.1.3 Das binäre (Basis 2), oktale (Basis 8), hexadezimale (Basis 16) Stellenwertsystem
Pfeil 21.1.4 Auswirkung der Typumwandlung auf die Bit-Muster
Pfeil 21.1.5 Vorzeichenlos arbeiten
Pfeil 21.1.6 Die Verschiebeoperatoren
Pfeil 21.1.7 Ein Bit setzen, löschen, umdrehen und testen
Pfeil 21.1.8 Bit-Methoden der Integer- und Long-Klasse
Pfeil 21.2 Fließkomma-Arithmetik in Java
Pfeil 21.2.1 Spezialwerte für Unendlich, Null, NaN
Pfeil 21.2.2 Standardnotation und wissenschaftliche Notation bei Fließkommazahlen *
Pfeil 21.2.3 Mantisse und Exponent *
Pfeil 21.3 Die Eigenschaften der Klasse Math
Pfeil 21.3.1 Attribute
Pfeil 21.3.2 Absolutwerte und Vorzeichen
Pfeil 21.3.3 Maximum/Minimum
Pfeil 21.3.4 Runden von Werten
Pfeil 21.3.5 Rest der ganzzahligen Division *
Pfeil 21.3.6 Division mit Rundung in Richtung negativ unendlich, alternativer Restwert *
Pfeil 21.3.7 Multiply-Accumulate
Pfeil 21.3.8 Wurzel- und Exponentialmethoden
Pfeil 21.3.9 Der Logarithmus *
Pfeil 21.3.10 Winkelmethoden *
Pfeil 21.3.11 Zufallszahlen
Pfeil 21.4 Genauigkeit, Wertebereich eines Typs und Überlaufkontrolle *
Pfeil 21.4.1 Der größte und der kleinste Wert
Pfeil 21.4.2 Überlauf und alles ganz exakt
Pfeil 21.4.3 Was bitte macht eine ulp?
Pfeil 21.5 Zufallszahlen: Random, SecureRandom und SplittableRandom
Pfeil 21.5.1 Die Klasse Random
Pfeil 21.5.2 Random-Objekte mit dem Samen aufbauen
Pfeil 21.5.3 Einzelne Zufallszahlen erzeugen
Pfeil 21.5.4 Pseudo-Zufallszahlen in der Normalverteilung *
Pfeil 21.5.5 Strom von Zufallszahlen generieren *
Pfeil 21.5.6 Die Klasse SecureRandom *
Pfeil 21.5.7 SplittableRandom *
Pfeil 21.6 Große Zahlen *
Pfeil 21.6.1 Die Klasse BigInteger
Pfeil 21.6.2 Beispiel: Ganz lange Fakultäten mit BigInteger
Pfeil 21.6.3 Große Fließkommazahlen mit BigDecimal
Pfeil 21.6.4 Mit MathContext komfortabel die Rechengenauigkeit setzen
Pfeil 21.6.5 Noch schneller rechnen durch mutable Implementierungen
Pfeil 21.7 Mathe bitte strikt *
Pfeil 21.7.1 Strikte Fließkommaberechnungen mit strictfp
Pfeil 21.7.2 Die Klassen Math und StrictMath
Pfeil 21.8 Geld und Währung
Pfeil 21.8.1 Geldbeträge repräsentieren
Pfeil 21.8.2 ISO 4217
Pfeil 21.8.3 Währungen in Java repräsentieren
Pfeil 21.9 Zum Weiterlesen
 

Zum Seitenanfang

21    Bits und Bytes, Mathematisches und Geld Zur vorigen ÜberschriftZur nächsten Überschrift

»Es gibt nur 10 Typen von Menschen auf der Welt.

Die, die Binärcode verstehen, und die, die ihn nicht verstehen.«

Dieses Kapitel betrachtet die Repräsentationen der Zahlen genauer und erläutert, wie binäre Operatoren auf diesen Werten arbeiten. Nachdem die Ganzzahlen genauer beleuchtet wurden, folgen eine detaillierte Darstellung der Fließkommazahlen und anschließend mathematische Grundfunktionen wie max(…), sin(…), abs(…), die in Java durch die Klasse Math realisiert werden.

 

Zum Seitenanfang

21.1    Bits und Bytes Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Bit ist ein Informationsträger für die Aussage »wahr oder falsch«, also mit zwei Zuständen. Durch das Zusammensetzen von einzelnen Bits entstehen größere Folgen wie das Byte, das aus 8 Bit besteht, zum Beispiel 00010011bin. Da die genannte Zahl durch die Ziffern 0 und 1 durchaus eine Dezimalzahl sein könnte, stellen wir hinter die Zahl ein kleines bin, wenn es zu Missverständnissen kommen kann.

Werden 8 Bit = 1 Byte zugrunde gelegt, lassen sich durch unterschiedliche Belegungen 256 unterschiedliche Zahlen bilden. Ist kein Bit des Bytes gesetzt, so ist die Zahl 0; ist jedes Bit gesetzt, ist die Zahl 255. Jedes Bit an einer Position im Byte kann 0 oder 1 sein, und daraus ergibt sich eine Wertigkeit, an welcher Stelle 0 oder 1 in der Folge steht. Wir sprechen von einem Stellenwertsystem, bei dem die Position einer Ziffer entscheidend ist, denn 12 ist nicht gleich 21, und 10bin ist nicht gleich 01bin. Jetzt stellt sich nur noch die Frage, wie die Bitnummerierung erfolgt, denn das Bit mit dem niedrigsten Stellenwert könnte links oder rechts stehen – üblicherweise steht es rechts, und das Bit mit dem höchsten Stellenwert steht links.

Beim Dualsystem haben wir die Ziffern 0 und 1. Der Wert einer Dualzahl ergibt sich durch Addition der Ziffern 0 oder 1, die vorher jeweils mit ihrem Stellenwert 2^i multipliziert werden. Die Wertebelegung für die Zahl 19 berechnet sich aus 16 + 2 + 1, da sie aus einer Anzahl von Summanden der Form 2i  zusammengesetzt ist: 19dez = 16 + 2 + 1 = 1 × 24 + 0 × 23 + 0 × 22 + 1 × 21 + 1 × 20 = 10011bin.

Bit

7

6

5

4

3

2

1

0

Wertigkeit

27=128

26=64

25=32

24=16

23=8

22=4

21=2

20=1

Belegung für 19

0

0

0

1

0

0

1

1

Tabelle 21.1    Wertebelegung

Java bietet zum Modifizieren von Bits

  • bitweise Operatoren und

  • bitweise Verschiebungen (Schiebeoperationen).

Diese schauen wir uns jetzt an.

 

Zum Seitenanfang

21.1.1    Die Bit-Operatoren Komplement, Und, Oder und XOR Zur vorigen ÜberschriftZur nächsten Überschrift

Mit Bit-Operatoren lassen sich Binäroperationen auf Operanden durchführen, um beispielsweise ein Bit eines Bytes zu setzen. Zu den Bit-Operationen zählen das Komplement eines Operanden und Verknüpfungen mit anderen Werten. Durch die bitweisen Operatoren können einzelne Bits abgefragt und manipuliert werden.

Operator

Bezeichnung

Aufgabe

~

Komplement

(bitweises Nicht)

Invertiert jedes Bit: Aus 0 wird 1, aus 1 wird 0.

|

bitweises Oder

Bei a | b wird jedes Bit von a und b einzeln Oder-verknüpft.

&

bitweises Und

Bei a & b wird jedes Bit von a und b einzeln

Und-verknüpft.

^

bitweises exklusives

Oder (XOR)

Bei a ^ b wird jedes Bit von a und b einzeln

XOR-verknüpft; es ist kein a hoch b.

Tabelle 21.2    Bit-Operatoren in Java

Das Komplement ist ein unärer Operator, die anderen sind binäre Operatoren. Betrachten wir allgemein die binäre Verknüpfung a # b. Bei der binären bitweisen Und-Verknüpfung mit & gilt für jedes Bit: Ist im Operand a irgendein Bit gesetzt und an gleicher Stelle auch im Operand b, so ist auch das Bit an der Stelle im Ergebnis gesetzt. Bei der Oder-Verknüpfung mit | muss nur einer der Operanden gesetzt sein, damit das Bit im Ergebnis gesetzt ist. Bei einem exklusiven Oder (XOR) ist das Ergebnis 1, wenn nur genau einer der Operanden 1 ist. Sind beide gemeinsam 0 oder 1, ist das Ergebnis 0. Dies entspricht einer binären Addition oder Subtraktion.[ 267 ](Auf XOR können wir im Grunde verzichten, weil es sich durch die anderen Operatoren ausdrücken lässt: a^b ist wie (a & ~b) | (~a & b). )

Fassen wir das Ergebnis noch einmal in einer Tabelle zusammen:

Bit 1

Bit 2

~Bit 1

Bit 1 & Bit 2

Bit 1 | Bit 2

Bit 1 ^ Bit 2

0

0

1

0

0

0

0

1

1

0

1

1

1

0

0

0

1

1

1

1

0

1

1

0

Tabelle 21.3    Die Bit-Operatoren Komplement, Und, Oder und XOR in einer Wahrheitstafel

Nehmen wir zum Beispiel zwei Ganzahlen:

Binär

Dezimal

Zahl 1

010011

16 + 2 + 1 = 19

Zahl 2

100010

32 + 2 = 34

Zahl 1 & Zahl 2

000010

19 & 34 = 2

Zahl 1 | Zahl 2

110011

19 | 34 = 51

Zahl 1 ^ Zahl 2

110001

19 ^ 34 = 49

Tabelle 21.4    Binäre Verknüpfung zweier Ganzzahlen

 

Zum Seitenanfang

21.1.2    Repräsentation ganzer Zahlen in Java – das Zweierkomplement Zur vorigen ÜberschriftZur nächsten Überschrift

Der Rechner speichert Daten in den Zuständen 0 und 1 und muss positive sowie negative Zahlen darstellen können. Dazu gibt es unterschiedliche Kodierungen, wie das Einer- oder Zweierkomplement, wobei Letzteres für moderne Programme üblich ist, auch weil die internen Schaltungen im Prozessor einfacher sind.

Das Zweierkomplement definiert für positive und negative Ganzzahlen folgende Kodierung:

  • Das Vorzeichen einer Zahl bestimmt ein Bit, das 1 bei negativen und 0 bei positiven Zahlen ist.

  • Um eine 0 darzustellen, ist kein Bit gesetzt.

  • Bei negativen Zahlen: Wir nehmen den Absolutwert der Zahl, invertieren das Bitmuster und addieren 1 zu dem Ergebnis.

[zB]  Beispiel

Testen wir das an der Zahl –1: Der Absolutwert ist 1, das Bitmuster (für 16 Bit) ist 0000 0000 0000 0001. Die Negation ergibt 1111 1111 1111 1110. Addieren wir 1, folgt 1111 1111 1111 1111.

Java-Ganzzahldatentypen im Zweierkomplement

Java kodiert die Ganzzahldatentypen byte, short, int und long immer im Zweierkomplement (der Datentyp char definiert keine negativen Zahlen). Mit dieser Kodierung gibt es eine negative Zahl mehr als positive, da es im Zweierkomplement keine positive und negative 0 gibt, sondern nur eine »positive« mit der Bit-Maske 0000…0000.

Dezimal

Binär

Hexadezimal

–32.768

1000 0000 0000 0000

80 00

–32.767

1000 0000 0000 0001

80 01

–32.766

1000 0000 0000 0010

80 02

–2

1111 1111 1111 1110

FF FE

–1

1111 1111 1111 1111

FF FF

0

0000 0000 0000 0000

00 00

1

0000 0000 0000 0001

00 01

2

0000 0000 0000 0010

00 02

32.766

0111 1111 1111 1110

7F FE

32.767

0111 1111 1111 1111

7F FF

Tabelle 21.5    Darstellungen des Zweierkomplements im Datentyp »short«

Bei allen negativen Ganzzahlen ist also das oberste Bit mit 1 gesetzt.

[»]  Hinweis

Zweierkomplement und Komplement (bitweises Nicht) haben eine interessante Beziehung. ~0 invertiert alle Bits, was im Ergebnis -1 ergibt. Allgemein ist ~a gleich -(a+1). Also ist zum Beispiel ~5 gleich –(5+1) = -6. Auch negiert gilt das: ~-5 ist gleich -(-5+1) = 4. Und ~-~-~-~-5 ist 1; damit lassen sich tolle Späße machen …

 

Zum Seitenanfang

21.1.3    Das binäre (Basis 2), oktale (Basis 8), hexadezimale (Basis 16) Stellenwertsystem Zur vorigen ÜberschriftZur nächsten Überschrift

Die Literale für Ganzzahlen lassen sich in vier unterschiedlichen Stellenwertsystemen angeben. Das natürlichste ist das Dezimalsystem (auch Zehnersystem genannt), bei dem die Literale aus den Ziffern 0 bis 9 bestehen. Zusätzlich existieren die Binär-, Oktal- und Hexadezimalsysteme, die die Zahlen zur Basis 2, 8 und 16 schreiben. Bis auf Dezimalzahlen beginnen die Zahlen in anderen Formaten mit einem besonderen Präfix.

Präfix

Stellenwertsystem

Basis

Darstellung von 1

0b oder 0B

binär

2

0b1 oder 0B1

0

oktal

8

01

kein

dezimal

10

1

0x oder 0X

hexadezimal

16

0x1 oder 0X1

Tabelle 21.6    Die Stellenwertsysteme und ihre Schreibweise

Ein hexadezimaler Wert beginnt mit 0x oder 0X. Da zehn Ziffern für 16 hexadezimale Zahlen nicht ausreichen, besteht eine Zahl zur Basis 16 zusätzlich aus den Buchstaben a bis f (bzw. A bis F). Das Hexadezimalsystem heißt auch Sedezimalsystem.[ 268 ](Das Präfix »octo« bei »Oktalsystem« stammt aus dem Lateinischen. Das Wort »hexadezimal« enthält zwei Bestandteile aus zwei verschiedenen Sprachen: »hexa« stammt aus dem Griechischen und »decem« (zehn) aus dem Lateinischen. Die alternative Bezeichnung Sedezimalzahl bzw. sedezimal (engl. sexadecimal – nicht sexagesimal, das ist Basis 60) ist rein aus dem Lateinischen abgeleitet, aber im Deutschen unüblich. Über den Ursprung des Wortes »Hexadezimal« finden Sie mehr auf der englischen Wikipedia-Seite http://en.wikipedia.org/wiki/Hexadecimal#Etymology. )

Ein oktaler Wert beginnt mit dem Präfix 0. Mit der Basis 8 werden nur die Ziffern 0 bis 7 für oktale Werte benötigt. Der Name stammt von dem lateinischen »octo«, das auf Deutsch »acht« heißt. Das Oktalsystem war früher eine verbreitete Darstellung, da nicht mehr einzelne Bits solo betrachtet werden mussten, sondern 3 Bit zu einer Gruppe zusammengefasst wurden. In der Kommunikationselektronik ist das Oktalsystem noch weiterhin beliebt, spielt aber sonst keine Rolle.

Für Dualzahlen (also Binärzahlen zur Basis 2) verwenden wir das Präfix 0b oder 0B. Es sind nur die Ziffern 0 und 1 erlaubt, ein klassischer Fall für 2.

[zB]  Beispiel

Gib Dezimal-, Binär, Oktal- und Hexadezimalzahlen aus:

System.out.println( 1243 );         // 1243

System.out.println( 0b10111011 ); // 187

System.out.println( 01230 ); // 664

System.out.println( 0xcafebabe ); // -889275714

System.out.println( 0xC0B0L ); // 49328

In Java-Programmen sollten Oktalzahlen mit Bedacht eingesetzt werden. Wer aus optischen Gründen mit der 0 eine Zahl linksbündig auffüllt, erlebt eine Überraschung:

int i = 118;

int j = 012; // Oktal 012 ist dezimal 10
 

Zum Seitenanfang

21.1.4    Auswirkung der Typumwandlung auf die Bit-Muster Zur vorigen ÜberschriftZur nächsten Überschrift

Die Typumwandlung führt dazu, dass bei Ganzzahlen die oberen Bits einfach abgeschnitten werden. Bei einer Anpassung von Fließkommazahlen auf Ganzzahlen wird gerundet. Was genau passiert, soll dieser Abschnitt zeigen.

Explizite Typumwandlung bei Ganzzahlen

Bei der Konvertierung eines größeren Ganzzahltyps in einen kleineren werden die oberen Bits abgeschnitten. Eine Anpassung des Vorzeichens findet nicht statt. Die Darstellung in Bit zeigt das sehr anschaulich:

Listing 21.1    src/main/java/com/tutego/insel/math/TypecastDemo.java, main()

int   ii = 123456789;     // 0000011101011011_11001101_00010101

int ij = -123456; // 1111111111111110_00011101_11000000



short si = (short) ii; // 11001101_00010101

short sj = (short) ij; // 00011101_11000000



System.out.println( si ); // -13035

System.out.println( sj ); // 7616

si wird eine negative Zahl, da das 16. Bit beim int ii gesetzt war und nun beim short das negative Vorzeichen anzeigt. Die Zahl hinter ij hat kein 16. Bit gesetzt, und so wird das short sj positiv.

Umwandlung von short und char

Ein short hat wie ein char eine Länge von 16 Bit. Doch diese Umwandlung ist nicht ohne ausdrückliche Konvertierung möglich. Das liegt am Vorzeichen von short. Zeichen sind per Definition immer ohne Vorzeichen. Würde ein char mit einem gesetzten höchstwertigen letzten Bit in ein short konvertiert, käme eine negative Zahl heraus. Ebenso wäre, wenn ein short eine negative Zahl bezeichnet, das oberste Bit im char gesetzt, was unerwünscht ist. Die ausdrückliche Umwandlung erzeugt immer nur positive Zahlen.

Der Verlust bei der Typumwandlung von char nach short tritt etwa bei der Han-Zeichenkodierung für chinesische, japanische oder koreanische Zeichen auf, weil dort im Unicode das erste Bit gesetzt ist, das bei der Umwandlung in ein short dem nicht gesetzten Vorzeichen-Bit weichen muss.

Typumwandlungen von int und char

Die Methode printXXX(…) ist mit den Typen char und int überladen, und eine Typumwandlung führt zur gewünschten Ausgabe:

int  c1 = 65;

char c2 = 'A';

System.out.println( c1 ); // 65

System.out.println( (int)c2 ); // 65

System.out.println( (char)c1 ); // A

System.out.println( c2 ); // A

System.out.println( (char)(c1 + 1) ); // B

System.out.println( c2 + 1 ); // 66

Einen Ganzzahlwert in einem int können wir als Zeichen ausgeben, genauso wie eine char-Variable als Zahlenwert. Wir sollten beachten, dass eine arithmetische Operation auf char-Typen zu einem int führt. Daher funktioniert für ein char c Folgendes nicht:

c = c + 1;

Richtig wäre:

c = (char)(c + 1)

Unterschiedliche Wertebereiche bei Fließ- und Ganzzahlen

Natürlich kann die Konvertierung doublelong nicht verlustfrei sein. Wie sollte das auch gehen? Zwar verfügt sowohl ein long als auch ein double über 64 Bit zur Datenspeicherung, aber ein double kann eine Ganzzahl nicht so effizient speichern wie ein long und hat etwas »Overhead« für einen großen Exponenten. Bei der impliziten Konvertierung eines long in einen double können einige Bit als Informationsträger herausfallen, wie das folgende Beispiel illustriert:

long   l = 1111111111111111111L; // 1111111111111111111

double d = l; // 1111111111111111170 (1.11111111111111117E18)

long m = (long) d; // 1111111111111111168

Java erlaubt ohne explizite Anpassung die Konvertierung eines long in einen double und auch in ein noch kleineres float, was vielleicht noch merkwürdiger ist, da float nur eine Genauigkeit von 6 bis 7 Stellen hat, long hingegen 18 Stellen hat.

long  l = 1000000000000000000L;

float f = l;

System.out.printf( "%f", f ); // 999999984306749440,000000

Materialverlust durch Überläufe *

Überläufe bei Berechnungen können zu schwerwiegenden Fehlern führen, so wie beim Absturz der Ariane 5 am 4. Juni 1996, genau 36,7 Sekunden nach dem Start. Die europäische Raumfahrtbehörde European Space Agency (ESA) hatte die unbemannte Rakete, die vier Satelliten an Bord hatte, von Französisch-Guayana aus gestartet. Glücklicherweise kamen keine Menschen ums Leben, doch der materielle Schaden belief sich auf etwa 500 Millionen US-Dollar. In dem Projekt steckten zusätzlich Entwicklungskosten von etwa 7 Milliarden US-Dollar. Der Grund für den Absturz war ein Rundungsfehler im Ada-Programm, der durch die Umwandlung einer 64-Bit-Fließkommazahl (die horizontale Geschwindigkeit) in eine vorzeichenbehaftete 16-Bit-Ganzzahl auftrat. Die Zahl war leider größer als 215 – 1 und die Umwandlung nicht gesichert, da die Programmierer diesen Zahlenbereich nicht angenommen hatten. Das führte zu einer Ausnahme, und als Konsequenz brach das Lenksystem zusammen, und die Selbstzerstörung wurde ausgelöst, da die Triebwerke abzubrechen drohten. Das wirklich Dumme an dieser Geschichte ist, dass die Software nicht unbedingt für den Flug notwendig war und nur den Startvorbereitungen diente. Im Fall einer Unterbrechung während des Countdowns hätte das Programm schnell abgebrochen werden können. Ungünstig war, dass der Programmteil unverändert durch Wiederverwendung per Copy & Paste aus der Ariane-4-Software kopiert worden war, die Ariane 5 aber schneller flog.

 

Zum Seitenanfang

21.1.5    Vorzeichenlos arbeiten Zur vorigen ÜberschriftZur nächsten Überschrift

Bis auf char sind in Java alle integralen Datentypen vorzeichenbehaftet und im Zweierkomplement kodiert. Bei einem byte stehen 8 Bit für die Kodierung eines Werts zur Verfügung, jedoch sind es eigentlich nur 7 Bit, denn über ein Bit erfolgt die Kodierung des Vorzeichens. Der Wertebereich reicht von –128 bis +127. Über einen Umweg ist es möglich, den vollen Wertebereich auszuschöpfen und so zu tun, als ob Java vorzeichenlose Datentypen hätte.

byte als vorzeichenlosen Datentyp nutzen

Eine wichtige Eigenschaft der expliziten Typumwandlung bei Ganzzahltypen ist, dass die überschüssigen Bytes einfach abgeschnitten werden. Betrachten wir die Typumwandlung an einem Beispiel:

int  l =        0xABCD6F;

byte b = (byte) 0xABCD6F;

System.out.println( Integer.toBinaryString( l ) ); // 101010111100110101101111

System.out.println( Integer.toBinaryString( b ) ); // 1101111

Liegt eine Zahl im Bereich von 0 bis 255, so kann ein byte sie durch seine 8 Bit grundsätzlich speichern. Java muss jedoch mit der expliziten Typumwandlung gezwungen werden, das Vorzeichen-Bit zu ignorieren. Erst dann entspricht die Zahl 255 acht gesetzten Bits, denn sie mit byte b = 255; zu belegen, funktioniert nicht.

Damit die Weiterverarbeitung gelingt, muss noch eine andere Eigenschaft berücksichtigt werden. Sehen wir uns dazu folgende Ausgabe an:

byte b1 = (byte) 255;

byte b2 = -1;

System.out.println( b1 ); // -1

System.out.println( b2 ); // -1

Das Bit-Muster ist in beiden Fällen gleich, alle Bits sind gesetzt. Dass die Konsolenausgabe aber negativ ist, hat mit einer anderen Java-Eigenschaft zu tun: Java konvertiert das Byte, das vorzeichenbehaftet ist, bei der Weiterverarbeitung in ein int (der Parametertyp bei der Methode ist toBinaryString(int)), und bei dieser Konvertierung wandert das Vorzeichen vom byte zum int weiter. Das folgende Beispiel zeigt das bei der Binärausgabe:

byte b = (byte) 255;

int i = 255;

System.out.printf( "%d %s%n", b, Integer.toBinaryString(b) );

// -1 11111111111111111111111111111111

System.out.printf( "%d %s%n", i, Integer.toBinaryString(i) );

// 255 11111111

Die Belegung der unteren 8 Bit vom byte b und int i ist identisch. Aber während beim int die oberen 3 Byte wirklich null sind, füllt Java durch die automatische Anpassung des Vorzeichens bei der Konvertierung von byte nach int im Zweierkomplement auch die oberen 3 Byte mit 255 auf. Soll ohne Vorzeichen weitergerechnet werden, stört das. Diese automatische Anpassung nimmt Java immer vor, wenn mit byte/short gerechnet wird, und nicht nur wie in unserem Beispiel, wenn eine Methode den Datentyp int fordert.

Um bei der Weiterverarbeitung einen Datenwert zwischen 0 und 255 zu bekommen, also das Byte eines int vorzeichenlos zu sehen, schneiden wir mit der Und-Verknüpfung die unteren 8 Bit heraus – alle anderen Bits bleiben also ausgenommen:

byte b = (byte) 255;

System.out.println( b ); // -1

System.out.println( b & 0xff ); // 255

Es bleibt zu bemerken, dass die Und-Verknüpfung zu dem Zieltyp int führt.

Bibliotheksmethoden für vorzeichenlose Behandlung

Immer ein & 0xff an einen Ausdruck zu setzen, um die oberen Bytes auszublenden, ist zwar nicht sonderlich aufwendig, aber schön ist das auch nicht. Hübscher sind Methoden wie toUnsignedInt(byte), die mit dem Namen deutlich dokumentieren, was hier eigentlich passiert:

Methoden in Byte:

  • static int toUnsignedInt(byte x)

  • static long toUnsignedLong(byte x)

In Integer:

  • static long toUnsignedLong(int x)

  • static String toUnsignedString(int i, int radix)

  • static String toUnsignedString(int i)

  • static int parseUnsignedInt(String s, int radix)

  • static int compareUnsigned(int x, int y)

  • static int divideUnsigned(int dividend, int divisor)

  • static int remainderUnsigned(int dividend, int divisor)

In Long:

  • String toUnsignedString(long i, int radix)

  • static String toUnsignedString(long i)

  • static long parseUnsignedLong(String s, int radix)

  • static int compareUnsigned(long x, long y)

  • static long divideUnsigned(long dividend, long divisor)

  • static long remainderUnsigned(long dividend, long divisor)

In Short:

  • static int toUnsignedInt(short x)

  • static long toUnsignedLong(short x)

Neben den einfachen toUnsignedXXX(…)-Methoden in den Wrapper-Klassen gesellen sich Methoden hinzu, die auch die Konvertierung in einen String und das Parsen eines Strings ermöglichen. Bei Integer und Long lassen sich ebenfalls neue Methoden ablesen, die Vergleiche, Division und Restwertbildung vorzeichenlos durchführen.

Konvertierungen von byte in ein char

Mit einer ähnlichen Arbeitsweise können wir auch die Frage lösen, wie sich ein Byte, dessen int-Wert im Minusbereich liegt, in ein char konvertieren lässt. Der erste Ansatz über eine Typumwandlung (char) byte ist falsch, und auf der Ausgabe dürfte nur ein rechteckiges Kästchen oder ein Fragezeichen erscheinen:

byte b = (byte) 'ß';

System.out.println( (char) b ); // Ausgabe ist ?

Das Dilemma ist wieder die fehlerhafte Vorzeichenanpassung. Bei der Benutzung des Bytes wird es zuerst in ein int konvertiert. Das 'ß' wird dann zu –33. Im nächsten Schritt wird diese –33 dann in ein char umgesetzt. Das ergibt 65.503, was einen Unicode-Bereich trifft, der zurzeit kein Zeichen definiert. Es wird wohl auch noch etwas dauern, bis die ersten Außerirdischen uns neue Zeichensätze schenken. Gelöst wird der Fall wie oben, indem von b nur die unteren 8 Bit betrachtet werden. Das geschieht wieder durch ein Ausblenden über den Und-Operator. Damit ergibt sich korrekt:

char c = (char) (b & 0x00ff);

System.out.println( c ); // Ausgabe ist ß

In der Regel wird so eine explizite Anpassung selten im Code stehen, denn zur Konvertierung von Zeichenkodierungen bietet Java extra Klassen.

 

Zum Seitenanfang

21.1.6    Die Verschiebeoperatoren Zur vorigen ÜberschriftZur nächsten Überschrift

Unter Java gibt es drei Verschiebeoperatoren (engl. shift-operators), die die Bits eines Werts um eine gewisse Anzahl an Positionen verschieben können:

  • n << s: Linksverschieben der Bits von n um s Positionen

  • n >> s: Arithmetisches Rechtsverschieben um s Positionen mit Vorzeichen

  • n >>> s: Logisches Rechtsverschieben um s Positionen ohne Vorzeichen

Die binären Verschiebeoperatoren bewegen alle Bits eines Datenwortes (das Bit-Muster) nach rechts oder links. Bei der Verschiebung steht nach dem binären Operator, also im rechten Operanden, die Anzahl an Positionen, um die verschoben wird. Obwohl es nur zwei Richtungen gibt, muss noch der Fall betrachtet werden, ob das Vorzeichen bei der Rechtsverschiebung beachtet wird oder nicht. Das wird dann arithmetisches Verschieben (Vorzeichen bleibt erhalten) oder logisches Verschieben (Vorzeichen wird mit 0 aufgefüllt) genannt.

n << s

Die Bits des Operanden n werden unter Berücksichtigung des Vorzeichens s-mal nach links geschoben (bei jedem Schritt mit 2 multipliziert), was 2 hoch s ergibt. Der rechts frei werdende Bit-Platz wird immer mit 0 aufgefüllt. Das Vorzeichen ändert sich jedoch, sobald eine 1 von der Position MSB1 nach MSB geschoben wird. (MSB steht hier für Most Significant Bit, also das Bit mit der höchsten Wertigkeit in der binären Darstellung.)

[»]  Hinweis

Zwar ist der Datentyp des rechten Operanden erst einmal ein int bzw. long mit vollem Wertebereich, doch als Verschiebepositionen (Weite) sind bei int nur Werte bis 31 sinnvoll und für ein long Werte bis 63 Bit, da nur die letzten 5 bzw. 6 Bit berücksichtigt werden. Sonst wird immer um den Wert verschoben, der sich durch das Teilen durch 32 bzw. 64 als Rest ergibt, sodass x << 32 und x << 0 auch gleich sind.

System.out.println( 1 << 30 );  // 1073741824

System.out.println( 1 << 31 ); // -2147483648

System.out.println( 1 << 32 ); // 1

n >> s (arithmetisches Rechtsschieben)

Beim Verschieben nach rechts wird, je nachdem, ob das Vorzeichen-Bit gesetzt ist oder nicht, eine 1 oder eine 0 von links eingeschoben. Das linke Vorzeichen-Bit bleibt also unberührt.

[zB]  Beispiel
Consumer<Integer> printBinary = value -> {

String s = String.format( "%32s", Integer.toBinaryString( value ) );

System.out.println( s.replace( ' ', '0' ) );

};

printBinary.accept( 0b10000000_00000000__00000000_00000000 >> 0 );

printBinary.accept( 0b10000000_00000000__00000000_00000000 >> 1);

printBinary.accept( 0b10000000_00000000__00000000_00000000 >> 2 );



printBinary.accept( 0b10000000_00000000__00000000_00000000 >>> 0 );

printBinary.accept( 0b10000000_00000000__00000000_00000000 >>> 1);

printBinary.accept( 0b10000000_00000000__00000000_00000000 >>> 2 );

Die Ausgabe ist:

10000000000000000000000000000000

11000000000000000000000000000000

11100000000000000000000000000000

10000000000000000000000000000000

01000000000000000000000000000000

00100000000000000000000000000000
[»]  Hinweis

Ein herausgeschobenes Bit ist für immer verloren!

System.out.println( 65535 >> 8 );  // 255

System.out.println( 255 << 8 ); // 65280

Es ist 65.535 = 0xFFFF, und nach der Rechtsverschiebung 65.535 >> 8 ergibt sich 0x00FF = 255. Schieben wir nun wieder nach links, also 0x00FF << 8, dann ist das Ergebnis 0xFF00 = 65.280.

Bei den Ganzzahldatentypen folgt unter Berücksichtigung des immer vorhandenen Vorzeichens bei normalen Rechtsverschiebungen eine vorzeichenrichtige Ganzzahldivision durch 2.

n >>> s (logisches Rechtsschieben)

Der Operator >>> berücksichtigt das Vorzeichen der Variablen nicht, sodass eine vorzeichenlose Rechtsverschiebung ausgeführt wird. So werden auf der linken Seite (MSB) nur Nullen eingeschoben; das Vorzeichen wird mitgeschoben.

[zB]  Beispiel

Mit den Verschiebeoperatoren lassen sich die einzelnen Bytes eines größeren Datentyps, etwa eines 4 Byte großen int, einfach extrahieren:

byte b1 = (byte)(v >>> 24),

b2 = (byte)(v >>> 16),

b3 = (byte)(v >>> 8),

b4 = (byte)(v );

Bei einer positiven Zahl hat dies keinerlei Auswirkungen, und das Verhalten ist wie beim >>-Operator.

[zB]  Beispiel

Die Ausgabe ist für den negativen Operanden besonders spannend:

System.out.println(  64 >>> 1 );  // 32

System.out.println( -64 >>> 1 ); // 2147483616

Ein <<<-Operator ergibt keinen Sinn, da die Linksverschiebung ohnehin nur Nullen rechts einfügt.

 

Zum Seitenanfang

21.1.7    Ein Bit setzen, löschen, umdrehen und testen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Bit-Operatoren lassen sich zusammen mit den Verschiebeoperatoren gut dazu verwenden, ein Bit zu setzen respektive herauszufinden, ob ein Bit gesetzt ist. Betrachten wir die folgenden Methoden, die ein bestimmtes Bit setzen, abfragen, invertieren und löschen:

static int setBit( int n, int pos ) {

return n | (1 << pos);

}



static int clearBit( int n, int pos ) {

return n & ~(1 << pos);

}



static int flipBit( int n, int pos ) {

return n ^ (1 << pos);

}



static boolean testBit( int n, int pos ) {

int mask = 1 << pos;



return (n & mask) == mask;

// alternativ: return (n & 1<<pos) != 0;

}
 

Zum Seitenanfang

21.1.8    Bit-Methoden der Integer- und Long-Klasse Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klassen Integer und Long bieten eine Reihe von statischen Methoden zur Bit-Manipulation und zur Abfrage diverser Bit-Zustände von ganzen Zahlen. Die Schreibweise int|long kennzeichnet durch int die statischen Methoden der Klasse Integer und durch long die statischen Methoden der Klasse Long.

final class java.lang.Integer | java.lang.Long

extends Number

implements Comparable<Integer> | implements Comparable<Long>
  • static int Integer.bitCount(long i)

  • static int Long.bitCount(long i)

    Liefern die Anzahl gesetzter Bits.

  • static int Integer.reverse(int i)

  • static long Long.reverse(long i)

    Drehen die Reihenfolge der Bits um.

  • static int Integer.reverseBytes(int i)

  • static long Long.reverseBytes(long i)

    Setzen die Bytes in die umgekehrte Reihenfolge, also das erste Byte an die letzte Position, das zweite Byte an die zweitletzte Position usw.

  • static int Integer.rotateLeft(int i, int distance)

  • static long Long.rotateLeft(long i, int distance)

  • static int Integer.rotateRight(int i, int distance)

  • static long Long.rotateRight(long i, int distance)

    Rotieren die Bits um distance Positionen nach links oder nach rechts.

  • static int Integer.highestOneBit(int i)

  • static long Long.highestOneBit(long i)

  • static int Integer.lowestOneBit(int i)

  • static long lowestOneBit(long i)

    Das Ergebnis ist i, wobei alle Bits gelöscht sind mit Ausnahme des höchst-/niedrigstwertigen Bits. Anders gesagt: Liefern einen Wert, wobei nur das höchste (links stehende) bzw. niedrigste (rechts stehende) Bit gesetzt ist. Es ist also nur höchstens 1 Bit gesetzt; beim Argument 0 ist natürlich kein Bit gesetzt und das Ergebnis ebenfalls 0.

  • static int Integer.numberOfLeadingZeros(int i)

  • static long Long.numberOfLeadingZeros(long i)

  • static int Integer.numberOfTrailingZeros(int i)

  • static long Long.numberOfTrailingZeros(long i)

    Liefern die Anzahl der Null-Bits vor dem höchsten bzw. nach dem niedrigsten gesetzten Bit.

Als Beispiel zeigt Tabelle 21.7 Anwendungen der statischen Bit-Methoden der Klasse Long:

Statische Methode der Klasse Long

Methodenergebnis

highestOneBit(0b00011000 )

16

lowestOneBit(0b00011000)

8

numberOfLeadingZeros( Long.MAX_VALUE )

1

numberOfLeadingZeros( Long.MIN_VALUE )

0

numberOfTrailingZeros( 16 )

4

numberOfTrailingZeros( 3 )

0

bitCount( 8 + 4 + 1 )

3

rotateLeft( 12, 1 )

24

rotateRight( 12, 1 )

6

reverse( 0x00FF00FFF0FF000FL )

f000ff0fff00ff0016

reverseBytes( 0xFEDCBA9876543210L )

1032547698badcfe16

reverse( Long.MAX_VALUE )

–2

Tabelle 21.7    Statische Methoden der Klasse »Long«

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern