Za

Zahlen in Julia

7 Übungen

Über Zahlen

Julia ist eine Allzwecksprache, die sich für die meisten Programmieraufgaben einsetzen lässt. In der Praxis liegen die wichtigsten Anwendungsfälle jedoch in den Ingenieur- und Naturwissenschaften. Schnelle, vielseitige und anspruchsvolle numerische Berechnungen stehen im Mittelpunkt des Designs.

Ganzzahlen

Eine Ganzzahl ist eine „runde“ Zahl ohne Dezimalpunkt.

Im Konzept Grundlagen haben wir gesehen, dass ein Ganzzahlwert einer Variable zugewiesen werden kann, ohne einen Typ anzugeben.

Zur besseren Lesbarkeit können Unterstriche als Trennzeichen für Ziffern verwendet werden. Der Compiler ignoriert sie.

julia> x = 3
3

julia> typeof(x)
Int64

julia> large_number = 1_234_567_890
1234567890

Intern verwendet der Compiler den vorzeichenbehafteten Ganzzahltyp, der am besten zu deiner CPU passt. Auf modernen PCs ist das meist Int64, was für die meisten Aufgaben vollkommen ausreicht.

Typen werden in einem späteren Konzept ausführlicher behandelt. Vorerst sollten die numerischen Typen hoffentlich intuitiv genug sein, damit dieses Dokument Sinn ergibt.

Wie wir in einem späteren Konzept sehen werden, unterstützt Julia von Haus aus sehr große, mehrdimensionale Arrays. Diese können sowohl den verfügbaren Speicher als auch die Bandbreite für Datenübertragungen an ihre Grenzen bringen.

Um dem Programmierer eine feinere Kontrolle zu geben, ist es sowohl möglich als auch recht üblich, den gewünschten vorzeichenbehafteten oder vorzeichenlosen Ganzzahltyp anzugeben. Viele Julia-Lernende, die zuvor Python verwendet haben, möchten das vielleicht mit NumPy vergleichen, statt mit dem reinen Python.

julia> y = Int8(42)
42

julia> typeof(y)
Int8

julia> z = UInt32(1024)
0x00000400

Beachte, dass Julia vorzeichenlose Ganzzahlen standardmäßig im Hexadezimalformat anzeigt, weil die vorzeichenlosen Typen oft für Bit-Operationen auf niedriger Ebene verwendet werden.

Ganzzahlen können auch als Binär-, Oktal- oder Hexadezimalzahl eingegeben werden, mit den Präfixen 0b, 0o bzw. 0x.

julia> a = 0x10
0x10

julia> Int(a)
16

julia> b = 0b1010 # displays as hexadecimal
0x0a

Ganzzahlüberläufe und BigInt

Jeder Ganzzahltyp hat einen maximalen und einen minimalen Wert, den er speichern kann:

julia> typemax(Int8)
127

julia> typemin(Int8)
-128

Wenn du diesen gültigen Bereich verlässt, kommt es zum Ganzzahlüberlauf, mit Ergebnissen, die sehr seltsam erscheinen.

julia> c = Int8(126)
126

julia> c * c
4

Wir können uns vermutlich darauf einigen, dass 126 * 126 nicht gleich 4 sein sollte!

Eine ausführliche Erklärung erfordert Wissen darüber, wie CPU-Register Ganzzahlen speichern, was den Rahmen dieses Konzepts sprengt.

Das Problem lässt sich verringern, indem du einen „breiteren“ Typ verwendest, bis hin zu Int128 oder UInt128 (was „breiter“ bedeutet, wird weiter unten besprochen). Allerdings haben alle harte Grenzen, bevor es zu Überlaufproblemen kommt.

Eine Lösung ist die Verwendung des Typs BigInt, der nur durch den Speicher deines Computers begrenzt ist. Er ist Julias Umsetzung der Arithmetik mit beliebiger Genauigkeit.

julia> 2 ^ 70
0

julia> big = BigInt(2) ^ 70
1180591620717411303424

julia> UInt128(big)
0x00000000000000400000000000000000

Weil 2 hoch 70 nicht in einen Int64 passt, schlägt die erste Berechnung im obigen Beispiel fehl. Beachte allerdings, dass es keine Fehlermeldung gibt. Programmierer, aufgepasst!

Wandelt man 2 mit einer Typumwandlung in einen BigInt um, erhält man die richtige Antwort, und die hexadezimale Anzeige macht deutlicher, dass es sich um eine Zweierpotenz handelt.

Gleitkommazahlen

Es überrascht nicht, dass Gleitkommazahlen optional einen Dezimalpunkt und einen Bruchteil nach dem Punkt haben.

julia> f = 3.45
3.45

julia> typeof(f)
Float64

Natürlich wird auch die wissenschaftliche Notation unterstützt.

julia> avogadro = 6.02e23
6.02e23

Wie bei den Ganzzahlen ist der Standardtyp für die meisten Zwecke ausreichend, aber es gibt auch andere vorzeichenbehaftete Typen. Es gibt keine vorzeichenlosen Gleitkommatypen.

Als Abkürzung können Float32-Werte mit dem Suffix f0 erstellt werden.

julia> f32 = 4.56f0
4.56f0

julia> typeof(f32)
Float32

Andere Gleitkommagrößen benötigen, wie bei den Ganzzahlen, eine explizite Typumwandlung.

Die maximalen und minimalen Werte mögen überraschen:

julia> typemax(Float64)
Inf

julia> typemin(Float64)
-Inf

Unendlich ist ein gültiger Wert!

Der nutzbare Bereich von Gleitkommazahlen ist jedoch begrenzt: Sehr große Werte erhalten einfach den Wert Inf, und sehr kleine werden auf 0.0 gerundet.

Mit einem anderen Funktionspaar können wir diese Grenzen sehen: ungefähr ± 10 ^ 308.

julia> floatmax(Float64)
1.7976931348623157e308

julia> floatmin(Float64)
2.2250738585072014e-308

Außerdem ist die Genauigkeit begrenzt: Es wird eine große, aber endliche Anzahl signifikanter Stellen dargestellt (etwa 15 bei Float64).

Arithmetische Operatoren

Wie im Konzept Grundlagen besprochen, funktionieren arithmetische Operatoren meist genauso wie die normale Arithmetik, die man in der Schule lernt. Beachte, dass die Potenzierung ^ verwendet, nicht ** (beides ist in anderen Sprachen üblich).

2 + 3  # 5 (addition)
2 - 3  # -1 (subtraction)
2 * 3  # 6 (multiplication)
8 / 2  # 4.0 (division)
8 % 3  # 2 (remainder)
2 ^ 3  # 8 (exponentiation)

Ein paar Julia-spezifische Details sind jedoch erwähnenswert.

Multiplikation

julia> x = 4.2
4.2

julia> 2 * x
8.4

julia> 2x
8.4

julia> 2.4x
10.08

Das mag überraschen.

Es ist immer möglich, * als Infix-Operator zu verwenden, wie in den meisten anderen Programmiersprachen.

Julia wurde jedoch von Leuten entworfen, die glauben, dass Code so weit wie möglich wie mathematische Gleichungen aussehen sollte.

Da Variablennamen mit einem Buchstaben beginnen müssen, gilt es als implizite Multiplikation, wenn man dem Namen eine Zahl (Ganzzahl oder Gleitkommazahl) voranstellt.

Wenn wir zum Beispiel die Oberfläche einer Kugel wollen, könnten wir statt 4 * pi * r * r auch Folgendes tun:

julia> surface(r) = 4π * r^2
surface (generic function with 1 method)

julia> surface(3)
113.09733552923255

Obwohl π eine eingebaute Konstante ist, ist es auch ein (griechischer) Buchstabe. Der Parser braucht daher trotzdem ein explizites *, um π von r zu trennen.

Division

Die Verwendung von / als Infix-Operator liefert immer ein Gleitkommaergebnis, selbst bei Ganzzahlen als Eingabe.

Für die Ganzzahldivision gibt es mehr Möglichkeiten:

julia> 10 / 3
3.3333333333333335

julia> div(10, 3)
3

julia> 10 ÷ 3
3

julia> 10 // 3
10//3

Die Funktion div() dient der Ganzzahldivision, wobei das Ergebnis gegen null abgeschnitten wird: bei positiven Zahlen nach unten, bei negativen nach oben.

Als Synonym können wir den Infix-Operator ÷ verwenden, wieder mit dem Ziel, es mathematischer aussehen zu lassen. Wenn du einen Editor verwendest, der Julia unterstützt, gib dies als \div ein und drücke dann die <Tab>-Taste.

Der Operator // braucht später im Lehrplan ein eigenes Konzept.

Vorerst können wir einfach sagen, dass das Ergebnis von // eine „rationale“ Zahl ist, der formale Name für das, was die meisten Leute einen Bruch nennen.

Gemeinsame Faktoren werden aus dem Zähler und dem Nenner entfernt, sodass sich ein Verhältnis zweier Ganzzahlen in der sogenannten „vollständig gekürzten Form“ ergibt.

julia> rationalnum = 22 // 6
11//3

julia> typeof(rationalnum)
Rational{Int64}

Wir haben also rationale Zahlen. Was ist mit „irrationalen“ Zahlen?

julia> π
π = 3.1415926535897...

julia> typeof(π)
Irrational{:π}

Eine irrationale Zahl lässt sich nicht als Verhältnis von Ganzzahlen darstellen. Häufige Beispiele sind π, e (Eulersche Zahl) und viele Wurzeln wie √2 (die Quadratwurzel aus 2).

Julia versucht, Mathematik korrekt zu betreiben.

Umwandlung numerischer Typen

Das kann oft automatisch geschehen:

julia> x = 2 + 3.5
5.5

julia> typeof(x)
Float64

Wir haben einen Int64 zu einem Float64 addiert und ein Float64-Ergebnis erhalten.

Tatsächlich wurde die Ganzzahl vor der Addition stillschweigend in einen Float64 umgewandelt.

Julia kennt ein Konzept der „Breite“ numerischer Typen.

  • Bei Ganzzahlen und bei Gleitkommazahlen ist das einfach die Anzahl der Bits, die zum Speichern benötigt werden. Int64 ist also breiter als Int16.
  • Gleitkommazahlen gelten als breiter als Ganzzahlen, weil sie jeden Bruchteil speichern können.

Wenn in einem Ausdruck verschiedene Typen gemischt werden, wird jeder nach Bedarf auf den breitesten verwendeten Typ hochgestuft.

Um die Umwandlung zu erzwingen, können wir eine Ganzzahl in einen bestimmten Typ umwandeln, wie in Float64(5).

Alternativ kannst du einfach float(5) verwenden und den Compiler einen passenden Typ wählen lassen.

Umwandlungen von Gleitkomma- in Ganzzahlen sind zwangsläufig komplizierter. Was möchtest du mit dem Teil nach dem Dezimalpunkt machen?

  • Die Funktion round() rundet auf die nächste ganze Zahl, wobei Fälle wie 4.5 auf die nächste gerade ganze Zahl gerundet werden.
  • floor() rundet ab, ceil() rundet auf, trunc() rundet gegen null.
  • Der Versuch, direkt umzuwandeln, zum Beispiel mit Int32(), schlägt mit einem InexactError fehl.

Standardmäßig geben diese Funktionen jedoch nicht den Ganzzahltyp zurück, den du vielleicht wolltest. Der gewünschte Ausgabetyp kann angegeben werden.

julia> round(4.5)
4.0

julia> round(Int64, 4.5)
4

julia> round(Int, 4.5)  # => default integer type
4

julia> ceil(Int16, 4.3)
5

Das Runden auf eine bestimmte Anzahl von Stellen nach dem Dezimalpunkt ist auch mit dem Schlüsselwort digits möglich.

julia> round(π, digits=10)
3.1415926536

Weitere Einzelheiten findest du im Handbuch.

Division durch null

Wirft das nicht einfach einen Fehler? Tatsächlich ist die Lage nicht so einfach.

Die Ganzzahldivision mit ÷ oder // führt, wie du vielleicht erwartest, zu einem Fehler.

Die Gleitkommadivision mit / verfolgt einen eher ingenieurmäßigen Ansatz statt eines klassischen Informatik-Ansatzes:

julia> 2 / 0
Inf

julia> 0 / 0
NaN

Wie in einem vorherigen Abschnitt besprochen, ist Unendlich in Julia eine gültige Gleitkommazahl, dargestellt durch Inf.

Wenn auch der Zähler null ist, ist das Ergebnis mathematisch undefiniert. Julia behandelt es dann als „not a number“, dargestellt durch NaN.

Wenn dir das seltsam vorkommt, denk an den Kontext der Arbeit mit großen Arrays realer (und damit oft ziemlich unordentlicher) Daten. Um voranzukommen, ist es am besten, problematische Werte einfach zu markieren und weiterzumachen.

Eine endlose manuelle Prüfung der Werte wäre mühsam zu programmieren und würde die Laufzeitleistung sicher beeinträchtigen.

Bei jeder kleinen Störung mit einer Fehlermeldung abzubrechen, würde dein Programm bei den Nutzern sehr unbeliebt machen!

Vergleichen von Gleitkommawerten

Wie im Konzept Conditionals beschrieben, wird ein Gleichheitstest üblicherweise mit dem Operator == durchgeführt.

Das funktioniert gut für Ganzzahlen, Zeichen, Strings usw. Gleitkommawerte haben jedoch eine begrenzte Genauigkeit, und verschiedene Wege, dasselbe Ergebnis zu berechnen, können zu leicht unterschiedlichen Werten führen. Bei Float64 liegt das typischerweise um die 15. signifikante Stelle: ein kleiner Unterschied, aber keine „Gleichheit“.

Traditionell lautet der Rat an Programmierer: Verwende niemals == mit Gleitkommawerten, die Ergebnisse sind unvorhersehbar.

Eine weit verbreitete Alternative ist, den Absolutbetrag der Differenz gegen eine zulässige Toleranz zu prüfen (oft Epsilon oder ϵ genannt). Statt a == b verwendest du also abs(a - b) < epsilon.

Julia bietet mit der Funktion isapprox() eine sauberere Alternative.

Für eine absolute Toleranz lautet die Syntax isapprox(a, b, atol=epsilon). Das Schlüsselwort atol ist in diesem Fall erforderlich.

Die relative Toleranz ist oft nützlicher. Sie ist die Standardeinstellung, daher versucht isapprox(a, b), einen sinnvollen Wert für rtol zu wählen (die Regeln sind ziemlich kompliziert).

Eine relative Toleranz kann ebenfalls angegeben werden, als Bruchteil der verglichenen Werte. isapprox(a, b, rtol=0.01 prüft also, ob die Werte innerhalb von 1 % voneinander liegen.

Nach üblicher Julia-Manier gibt es einen mathematischen Operator als Synonym für den Standardfall: a ≈ b (wobei der Operator als \approx und dann <tab> eingegeben wird).

Verwandte zukünftige Konzepte

Neben rationalen Zahlen werden spätere Teile des Lehrplans behandeln:

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Zahlen

Das Üben ist gesperrt

Schalte 2 weitere Übungen frei, um Zahlen zu üben