JavaRush /Java-Blog /Random-DE /Das Gerät der reellen Zahlen

Das Gerät der reellen Zahlen

Veröffentlicht in der Gruppe Random-DE
Hallo! In der heutigen Vorlesung werden wir über Zahlen in Java und insbesondere über reelle Zahlen sprechen. Das Gerät der reellen Zahlen - 1Keine Panik! :) In der Vorlesung wird es keine mathematischen Schwierigkeiten geben. Wir werden über reelle Zahlen ausschließlich aus der Sicht unseres „Programmierers“ sprechen. Was sind also „reelle Zahlen“? Reelle Zahlen sind Zahlen, die einen Bruchteil haben (der Null sein kann). Sie können positiv oder negativ sein. Hier einige Beispiele: 15 56,22 0,0 1242342343445246 -232336,11 Wie funktioniert eine reelle Zahl? Ganz einfach: Es besteht aus einem ganzzahligen Teil, einem gebrochenen Teil und einem Vorzeichen. Bei positiven Zahlen wird das Vorzeichen normalerweise nicht explizit angegeben, bei negativen Zahlen hingegen schon. Zuvor haben wir ausführlich untersucht , welche Operationen mit Zahlen in Java ausgeführt werden können. Darunter waren viele mathematische Standardoperationen – Addition, Subtraktion usw. Es gab auch einige neue für Sie: zum Beispiel den Rest der Division. Doch wie genau funktioniert die Arbeit mit Zahlen im Computer? In welcher Form werden sie im Gedächtnis gespeichert?

Speichern reeller Zahlen im Speicher

Ich denke, es wird für Sie keine Entdeckung sein, dass Zahlen groß und klein sein können :) Sie können miteinander verglichen werden. Beispielsweise ist die Zahl 100 kleiner als die Zahl 423324. Hat dies Auswirkungen auf den Betrieb des Computers und unseres Programms? Eigentlich ja . Jede Zahl wird in Java durch einen bestimmten Wertebereich dargestellt :
Typ Speichergröße (Bits) Wertebereich
byte 8 Bit -128 bis 127
short 16 Bit -32768 bis 32767
char 16 Bit Ganzzahl ohne Vorzeichen, die ein UTF-16-Zeichen darstellt (Buchstaben und Zahlen)
int 32 Bit von -2147483648 bis 2147483647
long 64 Bit von -9223372036854775808 bis 9223372036854775807
float 32 Bit von 2 -149 bis (2-2 -23 )*2 127
double 64 Bit von 2 -1074 bis (2-2 -52 )*2 1023
Heute werden wir über die letzten beiden Typen sprechen – floatund double. Beide erfüllen die gleiche Aufgabe – die Darstellung von Bruchzahlen. Sie werden sehr oft auch „ Gleitkommazahlen“ genannt . Merken Sie sich diesen Begriff für die Zukunft :) Zum Beispiel die Zahl 2.3333 oder 134.1212121212. Recht seltsam. Es stellt sich schließlich heraus, dass zwischen diesen beiden Typen kein Unterschied besteht, da sie die gleiche Aufgabe erfüllen? Aber es gibt einen Unterschied. Achten Sie auf die Spalte „Größe im Speicher“ in der Tabelle oben. Alle Zahlen (und nicht nur Zahlen – alle Informationen im Allgemeinen) werden im Computerspeicher in Form von Bits gespeichert. Ein Bit ist die kleinste Informationseinheit. Es ist ziemlich einfach. Jedes Bit ist entweder gleich 0 oder 1. Und das Wort „ Bit “ selbst kommt vom englischen „ binary digit “ – einer Binärzahl. Ich denke, Sie haben wahrscheinlich schon von der Existenz des binären Zahlensystems in der Mathematik gehört. Jede uns bekannte Dezimalzahl kann als Menge von Einsen und Nullen dargestellt werden. Beispielsweise würde die Zahl 584,32 im Binärformat so aussehen: 100100100001010001111 . Jede Eins und Null in dieser Zahl ist ein separates Bit. Jetzt sollten Sie sich über den Unterschied zwischen den Datentypen im Klaren sein. Wenn wir beispielsweise eine Zahl vom Typ erstellen float, stehen uns nur 32 Bit zur Verfügung. Beim Erstellen einer Nummer floatwird genau so viel Speicherplatz im Speicher des Computers zugewiesen. Wenn wir die Zahl 123456789.65656565656565 erstellen möchten, sieht sie im Binärformat so aus: 11101011011110011010001010110101000000 . Es besteht aus 38 Einsen und Nullen, d. h. es werden 38 Bit benötigt, um es im Speicher zu speichern. floatDiese Nummer „passt“ einfach nicht in den Typ ! Daher kann die Zahl 123456789 als Typ dargestellt werden double. Für die Speicherung sind bis zu 64 Bit vorgesehen: Das passt zu uns! Natürlich wird auch der Wertebereich passen. Der Einfachheit halber können Sie sich eine Zahl als ein kleines Kästchen mit Zellen vorstellen. Wenn genügend Zellen vorhanden sind, um jedes Bit zu speichern, ist der Datentyp richtig gewählt :) Das Gerät der reellen Zahlen - 2Natürlich wirken sich unterschiedliche Mengen an zugewiesenem Speicher auch auf die Anzahl selbst aus. Bitte beachten Sie, dass Typen unterschiedliche Wertebereiche floathaben . doubleWas bedeutet das in der Praxis? Eine Zahl doublekann eine höhere Präzision ausdrücken als eine Zahl float. 32-Bit-Gleitkommazahlen (in Java ist dies genau der Typ float) haben eine Genauigkeit von etwa 24 Bit, also etwa 7 Dezimalstellen. Und 64-Bit-Zahlen (in Java ist dies der Typ double) haben eine Genauigkeit von etwa 53 Bit, also etwa 16 Dezimalstellen. Hier ist ein Beispiel, das diesen Unterschied gut verdeutlicht:
public class Main {

   public static void main(String[] args)  {

       float f = 0.0f;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}
Was sollen wir hier als Ergebnis erhalten? Es scheint, dass alles ganz einfach ist. Wir haben die Zahl 0,0 und addieren siebenmal hintereinander 0,1111111111111111 dazu. Das Ergebnis sollte 0,7777777777777777 sein. Aber wir haben eine Nummer geschaffen float. Seine Größe ist auf 32 Bit begrenzt und, wie bereits erwähnt, ist es in der Lage, eine Zahl bis etwa zur 7. Dezimalstelle anzuzeigen. Daher wird das Ergebnis, das wir in der Konsole erhalten, am Ende anders ausfallen als erwartet:

0.7777778
Die Nummer schien „abgeschnitten“ zu sein. Sie wissen bereits, wie Daten im Speicher gespeichert werden – in Form von Bits, daher sollte Sie das nicht überraschen. Es ist klar, warum das passiert ist: Das Ergebnis 0,7777777777777777 passte einfach nicht in die uns zugewiesenen 32 Bits, also wurde es gekürzt, um in eine Typvariable zu passen :) Wir können in unserem Beispiel floatden Typ der Variablen in ändern und dann den endgültigen doubleErgebnis wird nicht abgeschnitten:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}

0.7777777777777779
Es sind bereits 16 Nachkommastellen vorhanden, das Ergebnis „passt“ in 64 Bit. Vielleicht ist Ihnen übrigens aufgefallen, dass die Ergebnisse in beiden Fällen nicht ganz korrekt waren? Die Berechnung erfolgte mit geringfügigen Fehlern. Auf die Gründe dafür gehen wir weiter unten ein :) Lassen Sie uns nun ein paar Worte dazu sagen, wie Sie Zahlen miteinander vergleichen können.

Vergleich reeller Zahlen

Teilweise haben wir dieses Thema bereits in der letzten Vorlesung angesprochen, als wir über Vergleichsoperationen gesprochen haben. Wir werden Operationen wie >, <, nicht erneut analysieren >=. <=Schauen wir uns stattdessen ein interessanteres Beispiel an:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 10; i++) {
           f += 0.1;
       }

       System.out.println(f);
   }
}
Welche Zahl wird Ihrer Meinung nach auf dem Bildschirm angezeigt? Die logische Antwort wäre die Antwort: die Zahl 1. Wir beginnen mit dem Zählen bei der Zahl 0,0 und addieren nacheinander zehnmal hintereinander 0,1 dazu. Alles scheint richtig zu sein, es sollte eins sein. Versuchen Sie, diesen Code auszuführen, und die Antwort wird Sie sehr überraschen :) Konsolenausgabe:

0.9999999999999999
Aber warum ist in einem so einfachen Beispiel ein Fehler aufgetreten? O_o Hier könnte sogar ein Fünftklässler problemlos richtig antworten, aber das Java-Programm lieferte ein ungenaues Ergebnis. „Ungenau“ ist hier ein besseres Wort als „falsch“. Wir haben immer noch eine Zahl erhalten, die sehr nahe bei eins liegt und nicht nur irgendein Zufallswert :) Sie weicht buchstäblich um einen Millimeter von der richtigen ab. Aber warum? Vielleicht ist das nur ein einmaliger Fehler. Vielleicht ist der Computer abgestürzt? Versuchen wir, ein weiteres Beispiel zu schreiben.
public class Main {

   public static void main(String[] args)  {

       //Füge elf Mal hintereinander 0,1 zu Null hinzu
       double f1 = 0.0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multipliziere 0,1 mit 11
       double f2 = 0.1 * 11;

       //sollte in beiden Fällen gleich sein – 1,1
       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       // Lass uns das Prüfen!
       if (f1 == f2)
           System.out.println(„f1 und f2 sind gleich!“);
       else
           System.out.println(„f1 und f2 sind nicht gleich!“);
   }
}
Konsolenausgabe:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
Es handelt sich also eindeutig nicht um einen Computerfehler :) Was ist los? Fehler wie diese hängen mit der Art und Weise zusammen, wie Zahlen im Speicher des Computers in binärer Form dargestellt werden. Tatsache ist, dass es im Binärsystem unmöglich ist, die Zahl 0,1 genau darzustellen . Das Dezimalsystem hat übrigens auch ein ähnliches Problem: Es ist unmöglich, Brüche korrekt darzustellen (und statt ⅓ erhalten wir 0,33333333333333..., was ebenfalls nicht ganz das richtige Ergebnis ist). Es scheint eine Kleinigkeit zu sein: Bei solchen Berechnungen kann der Unterschied ein Hunderttausendstel (0,00001) oder sogar weniger betragen. Was aber, wenn das gesamte Ergebnis Ihres Very Serious Program von diesem Vergleich abhängt?
if (f1 == f2)
   System.out.println(„Rakete fliegt ins All“);
else
   System.out.println(„Der Start ist abgesagt, alle gehen nach Hause“);
Wir haben eindeutig erwartet, dass die beiden Zahlen gleich sind, aber aufgrund des internen Speicherdesigns haben wir den Raketenstart abgesagt. Das Gerät der reellen Zahlen - 3Wenn ja, müssen wir entscheiden, wie wir zwei Gleitkommazahlen vergleichen, damit das Ergebnis des Vergleichs ... ähm ... vorhersehbarer ist. Regel Nr. 1 beim Vergleich reeller Zahlen haben wir also bereits gelernt: Verwenden Sie beim Vergleich reeller Zahlen niemals ==Gleitkommazahlen. Ok, ich denke, das sind genug schlechte Beispiele :) Schauen wir uns ein gutes Beispiel an!
public class Main {

   public static void main(String[] args)  {

       final double threshold = 0.0001;

       //Füge elf Mal hintereinander 0,1 zu Null hinzu
       double f1 = .0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multipliziere 0,1 mit 11
       double f2 = .1 * 11;

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       if (Math.abs(f1 - f2) < threshold)
           System.out.println(„f1 und f2 sind gleich“);
       else
           System.out.println(„f1 und f2 sind nicht gleich“);
   }
}
Hier machen wir im Wesentlichen das Gleiche, ändern aber die Art und Weise, wie wir die Zahlen vergleichen. Wir haben eine spezielle „Schwellenwertzahl“ – 0,0001, ein Zehntausendstel. Es kann anders sein. Es kommt darauf an, wie präzise der Vergleich im Einzelfall sein muss. Sie können es größer oder kleiner machen. Mit der Methode Math.abs()erhalten wir den Modul einer Zahl. Der Modul ist der Wert einer Zahl unabhängig vom Vorzeichen. Beispielsweise haben die Zahlen -5 und 5 den gleichen Modul und sind gleich 5. Wir subtrahieren die zweite Zahl von der ersten, und wenn das resultierende Ergebnis unabhängig vom Vorzeichen kleiner als der von uns festgelegte Schwellenwert ist, dann unsere Zahlen sind gleich. Sie entsprechen in jedem Fall dem Genauigkeitsgrad, den wir anhand unserer „Schwellenzahl“ ermittelt haben , also mindestens bis auf ein Zehntausendstel. Diese Vergleichsmethode erspart Ihnen das unerwartete Verhalten, das wir im Fall von beobachtet haben ==. Eine weitere gute Möglichkeit, reelle Zahlen zu vergleichen, ist die Verwendung einer speziellen Klasse BigDecimal. Diese Klasse wurde speziell zum Speichern sehr großer Zahlen mit einem Bruchteil erstellt. Im Gegensatz zu doubleund werden floatbei der Verwendung BigDecimalvon Additionen, Subtraktionen und anderen mathematischen Operationen nicht Operatoren ( +-usw.), sondern Methoden verwendet. So wird es in unserem Fall aussehen:
import java.math.BigDecimal;

public class Main {

   public static void main(String[] args)  {

       /*Erstelle zwei BigDecimal-Objekte – Null und 0,1.
       Wir machen das Gleiche wie zuvor – addieren 0,1 11 Mal hintereinander zu Null.
       In der BigDecimal-Klasse erfolgt die Addition mit der add()-Methode */
       BigDecimal f1 = new BigDecimal(0.0);
       BigDecimal pointOne = new BigDecimal(0.1);
       for (int i = 1; i <= 11; i++) {
           f1 = f1.add(pointOne);
       }

       /*Auch hier hat sich nichts geändert: Erstellen Sie zwei BigDecimal-Objekte
       und multiplizieren Sie 0,1 mit 11.
       In der BigDecimal-Klasse erfolgt die Multiplikation mit der Methode multiply()*/
       BigDecimal f2 = new BigDecimal(0.1);
       BigDecimal eleven = new BigDecimal(11);
       f2 = f2.multiply(eleven);

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       /*Eine weitere Funktion von BigDecimal besteht darin, dass Zahlenobjekte mithilfe der speziellen Methode „compareTo()“ miteinander verglichen werden müssen
       */
       if (f1.compareTo(f2) == 0)
           System.out.println(„f1 und f2 sind gleich“);
       else
           System.out.println(„f1 und f2 sind nicht gleich“);
   }
}
Welche Art von Konsolenausgabe erhalten wir?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
Wir haben genau das Ergebnis erhalten, das wir erwartet hatten. Und achten Sie darauf, wie genau unsere Zahlen ausgefallen sind und wie viele Dezimalstellen hineinpassen! Viel mehr als in floatund sogar in double! Merken Sie sich den Kurs BigDecimalfür die Zukunft, Sie werden ihn auf jeden Fall brauchen :) Puh! Der Vortrag war ziemlich lang, aber Sie haben es geschafft: Gut gemacht! :) Wir sehen uns in der nächsten Lektion, zukünftiger Programmierer!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION