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.
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
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.
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).
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.
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.
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.
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.
Int64 ist also breiter als Int16.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?
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.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.
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!
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).
Neben rationalen Zahlen werden spätere Teile des Lehrplans behandeln:
2.3 + 4.5im.