Sprache
Zahlen im Detail
Antra rechnet so genau, wie es geht, und sagt dir, wenn es nicht mehr geht.
Ganzzahlen und Brüche sind exakt und haben keine Größengrenze. Ungenau wird es
nur, wenn eine Gleitkommazahl ins Spiel kommt, und das passiert nicht
versehentlich: Eine Gleitkommazahl entsteht nur aus Funktionen wie Math.sqrt,
aus einer Wurzel wie 2 ** 0.5 oder durch ein ausdrückliches float(...).
Dieses Kapitel beschreibt, welche Zahlen es gibt, wie sie entstehen, was beim
Rechnen herauskommt und wo die Grenzen liegen.
Als Typannotationen heißen die drei Arten Int, Rat und Float, alle
zusammen Number (Kapitel 16).
Drei Arten von Zahlen#
| Art | Prädikat | Beispiel | Eigenschaft |
|---|---|---|---|
| Ganzzahl (Int) | int? | 42, -7, 2 ** 100 | exakt, beliebig groß |
| Bruch (Rat) | rat? | 1/3, 0.5, 4 / 2 | exakt, Zähler und Nenner beliebig groß |
| Gleitkommazahl (Float) | float? | Math.sqrt(2) | ungenau, etwa 15 gültige Stellen |
Jede Zahl gehört zu genau einer der drei Arten. number? ist für alle drei
wahr, und type(...) liefert für alle drei :number.
import Antra::Math
println(int?(42)) # true
println(rat?(1/3)) # true
println(float?(Math.sqrt(2))) # true
println(number?(0.5)) # trueWoher eine Zahl ihre Art hat#
Die Art einer Zahl hängt davon ab, wie sie entsteht, nicht von ihrem Wert.
4 / 2 hat den Wert 2, ist aber ein Bruch, weil eine Division einen Bruch
liefert. Math.sqrt(4) hat ebenfalls den Wert 2, ist aber eine
Gleitkommazahl, weil Math.sqrt Gleitkommazahlen liefert.
import Antra::Math
println(rat?(4 / 2)) # true
println(int?(4 / 2)) # false
println(float?(Math.sqrt(4))) # true
println(int?(Math.sqrt(4))) # falseSo weißt du beim Lesen des Codes, was herauskommt, ohne den Wert zu kennen.
Literale#
| Schreibweise | Art |
|---|---|
42, -7, 10_000 | Int |
0xFF, 0o755, 0b1010 | Int (auch über 64 Bit: 0xFFFFFFFFFFFFFFFFFF) |
beliebig lange Ziffernfolge, z. B. 123456789012345678901234567890 | Int |
0.5, 1_000.25, 3.14 | Rat (exakt, kein Float!) |
1e3, 1e300, 6.02e23 | Int (1e300 ist eine 1 mit 300 Nullen) |
1.5e-3, 1e-20 | Rat (3/2000, 1/100000000000000000000) |
Dezimalzahlen sind in Antra also Brüche: 0.1 ist genau ein Zehntel, auch mit
beliebig vielen Stellen. Eine Exponent-Zahl ist ein Int, wenn ihr Wert ganz
ist, sonst ein Bruch. Ein Literal darf höchstens 10 000 Stellen ergeben,
1e20000 ist ein Fehler; als Rechnung 10 ** 20000 geht es.
Text, den num(...) liest, folgt denselben Regeln: num("2.5") ist ein
Bruch, num("12") ein Int und num("1e30") ein exakter Int; auch
num("1_000"), num("0xFF"), num("0o755") und num("0b1010") gehen.
Funktionen#
Funktionen, deren Ergebnis im Allgemeinen nicht exakt darstellbar ist, liefern
eine Gleitkommazahl, auch wenn das Ergebnis zufällig glatt ist: Math.sqrt,
Math.sin, Math.log, Math.exp und ihre Verwandten. float(...) macht aus
jeder Zahl ausdrücklich eine Gleitkommazahl. Dasselbe gilt für ** mit einem
Bruch als Exponenten und für Dezimalzahlen, die JSON.parse liest.
Was beim Rechnen herauskommt#
| Rechnung | Ergebnis |
|---|---|
Int + - * Int | Int |
Int / Int | Rat, auch wenn die Division aufgeht: 4 / 2 |
Rat mit Int oder Rat (+ - * /) | Rat |
| irgendetwas mit einer Float | Float |
Int ** Int (Exponent ≥ 0) | Int |
Int oder Rat ** Int (auch negativ) | Rat: (1/2) ** 2 ist 1/4, 2 ** -1 ist 1/2 |
** mit einem Bruch oder einer Float als Exponent | Float: 2 ** 0.5 ist die Wurzel aus 2 |
% | die Art wie bei + - *; Vorzeichen wie der rechte Operand |
println(7 / 3) # 7/3
println(7 / 2) # 3.5 (ein Bruch, als Dezimalzahl angezeigt)
println(1/3 + 1/6) # 0.5
println(2 ** 100) # 1267650600228229401496703205376
println(2 ** -1) # 0.5 (ein Bruch)
println(2 ** 0.5) # 1.4142135623731 (Float)
println(-7 % 3) # 2
println(7 % -3) # -2
println(7.5 % 2) # 1.5 (ein Bruch, exakt)
println(-7.5 % 2) # 0.5a % b ist der Rest a - b * Math.floor(a / b): er hat immer das Vorzeichen
von b (wie in Python, Ruby und Clojure, anders als % in C oder Java). Mit
Brüchen rechnet % exakt, mit einer Gleitkommazahl als Float; nichts wird
vorher auf eine ganze Zahl abgeschnitten.
Eine Gleitkommazahl „steckt an“: Sobald eine im Spiel ist, ist das Ergebnis wieder eine. Umgekehrt wird aus exakten Zahlen nie still eine ungenaue.
Teilen durch null ist immer ein Fehler, auch bei Gleitkommazahlen: x / 0,
x % 0 und 0 ** -1 (ebenso float(0) ** -1).
Exakt und unbegrenzt#
Ganzzahlen haben keine Obergrenze. Wird ein Ergebnis größer als 64 Bit, rechnet Antra einfach weiter:
println(9223372036854775807 + 1) # 9223372036854775808
println(2 ** 100 + 1) # 1267650600228229401496703205377Dasselbe gilt für Brüche. Zähler und Nenner dürfen beliebig groß werden:
my h = 0
my k = 1
while (k <= 100) { h = h + 1 / k; k = k + 1 }
println(h)
# 14466636279520351160221518043104131447711/2788815009188499086581352357412492142272Das ist die exakte Summe 1 + 1/2 + … + 1/100. Mit numerator und
denominator kommst du an die beiden Teile:
val r = 3/4
println(r.numerator) # 3
println(r.denominator) # 4Brüche werden immer gekürzt gespeichert, das Vorzeichen steht im Zähler. Ein
Int ist der Bruch n/1: (5).numerator ist 5, (5).denominator ist 1.
Wann float(...) sinnvoll ist#
Exaktheit hat einen Preis: Bei langen Rechenketten mit Brüchen wachsen Zähler und Nenner, und jede Rechnung wird langsamer. Typisch ist eine Schleife, die einen Wert immer wieder mit einem Dezimalfaktor multipliziert:
my v = 4
for 1..40 { v = v * -0.95 + 0.2 }
println(v.denominator) # eine Zahl mit 52 StellenWenn das Ergebnis nicht exakt sein muss, etwa bei Animationen, Physik oder Statistik, mach den Faktor zur Gleitkommazahl. Danach bleibt die Rechnung schnell, weil Float ansteckt:
my v = 4
for 1..40 { v = v * float(-0.95) + 0.2 }
println(float?(v)) # trueFür Geldbeträge, Brüche, Zählerstände und alles, bei dem jede Stelle zählt, bleibst du bei Int und Rat.
Vergleichen#
==, <, >, <=, >= und <=> vergleichen Zahlen nach ihrem Wert, über
alle drei Arten hinweg:
println(2 == 4/2) # true
println(1/2 == 0.5) # true
println(0.1 + 0.2 == 0.3) # true (alles Brüche, also exakt)Mit Gleitkommazahlen gelten die üblichen Rundungsfehler:
println(float(0.1) + float(0.2) == float(0.3)) # falseAuch zwischen einer exakten Zahl und einer Gleitkommazahl zählt der genaue
Wert, nicht der gerundete: (2 ** 64 + 1) > float(2 ** 64) ist true, und
1/3 == float(1/3) ist false, weil float(1/3) nur ungefähr ein Drittel ist.
NaN (etwa (-1) ** 0.5) ist keiner Zahl gleich, auch sich selbst nicht, und
hat keine Ordnung: ==, <, <=, >, >= sind false, != ist true,
<=> ist ein Fehler. Inf ist größer als jede exakte Zahl, auch eine jenseits
des Float-Bereichs.
=== ist strenger und vergleicht auch die Art: 2 === 4/2 ist false,
2 === float(2) ebenso, 1/2 === 0.5 ist true (beides Brüche).
Eine Zahl ist nie gleich einem String: 1 == "1" ist false (kein Fehler),
1 < "1" dagegen ein Typfehler. Wandle vorher mit num(...) oder str(...)
um.
Ranges mit Brüchen#
Eine Range rechnet mit derselben Art wie ihre Grenzen und ihr Schritt. Mit einem Bruch sind alle Werte exakte Brüche, sie werden nicht aufsummiert, und das Ende geht nicht verloren:
println(collect(0..0.3 by 0.1)) # [0, 0.1, 0.2, 0.3]
println(collect(1..2 by 1/3)) # [1, 4/3, 5/3, 2]
println(collect(0.5..<3)) # [0.5, 1.5, 2.5]
println(0.3 in 0..1 by 0.1) # trueInts jenseits von 64 Bit gehen genauso
(9007199254740992..9007199254740995).
Anzeige#
| Wert | Anzeige |
|---|---|
| Int | alle Ziffern: 1267650600228229401496703205376 |
| Rat mit Nenner 1 | wie ein Int: 4 / 2 zeigt 2 |
| Rat, dessen Nenner nur die Faktoren 2 und 5 hat | als Dezimalzahl: 1/4 zeigt 0.25 |
| jeder andere Rat | als Bruch: 1/3 zeigt 1/3 |
| Float | höchstens 15 gültige Stellen: 1.4142135623731 |
| ganzzahlige Float | ohne Nachkommastellen: Math.sqrt(4) zeigt 2 |
Die Anzeige verrät die Art also nicht immer. 2 kann ein Int, ein Rat oder
eine Float sein. Wenn es darauf ankommt, frag mit int?, rat? oder
float?.
Die Dezimalanzeige eines Bruchs ist auf 15 gültige Stellen gerundet, der Wert
selbst bleibt exakt. 1 / (2 ** 70) zeigt 8.470329472543e-22 und rechnet
trotzdem mit dem genauen Bruch weiter.
Umwandeln und Runden#
| Funktion | Ergebnis |
|---|---|
num("12"), num("2.5") | Zahl aus einem String, gelesen wie ein Literal: 12 ist ein Int, 2.5 ein Bruch |
int(x) | Int, Richtung null abgeschnitten: int(-7/2) ist -3 |
float(x) | Float |
rat(x) | Rat: rat("3/4"), rat(5); aus einer Gleitkommazahl ihr genauer Wert (rat(float(0.1)) == float(0.1)), die angezeigte Dezimalzahl mit rat(str(x)) |
str(x) | die Anzeige als String |
Math.floor(x) | Int, abgerundet: Math.floor(-7/2) ist -4 |
Math.ceil(x) | Int, aufgerundet |
Math.round(x) | Int, Hälften nach oben: Math.round(5/2) ist 3, Math.round(-5/2) ist -2 |
Math.trunc(x) | Int, Richtung null |
Math.abs, Math.min, Math.max | behalten die Art: Math.min(1/3, 1/2) ist 1/3 |
Die Rundungsfunktionen rechnen bei Brüchen exakt und liefern immer einen Int,
auch für eine Gleitkommazahl; NaN und Inf haben keinen und sind ein Fehler
(wie bei int(...)). Brauchst du aus einer Rechnung mit Math.sqrt
eine ganze Zahl, etwa als Index oder Anzahl, nimm sie:
import Antra::Math
val n = Math.floor(Math.sqrt(10))
println(n) # 3
println(int?(n)) # trueZahlen als Map-Keys#
Map-Keys sind Text. Eine Zahl wird als Key zu ihrem exakten Text: Ein Bruch
mit endlicher Dezimaldarstellung wird voll ausgeschrieben (0.5), jeder andere
als n/d, und eine Gleitkommazahl mit so vielen Stellen, wie sie braucht.
Gleiche Werte landen deshalb im selben Eintrag, verschiedene nie:
my m = %{}
m[1/2] := "a"
m[0.5] := "b"
println(m) # %{ '0.5' => 'b' }
println(m["0.5"]) # bFür Set-Elemente gilt dasselbe: %<1, 2/2> hat ein Element, zwei
Gleitkommazahlen, die sich nur in der letzten Stelle unterscheiden, sind zwei.
Grenzen#
- Bit-Operatoren (
&,|,^,<<,>>,~) arbeiten auf 64-Bit- Ganzzahlen. Mit einem größeren Int sind sie ein Fehler, ebensoInt.bit_count,Int.bit_length, die Rotationen undInt.to_hex. Ein ganzer Bruch oder eine ganze Gleitkommazahl zählt als ihr Int ((6/2) & 1),2.5 & 1ist ein Fehler statt abgeschnitten. Ab 64 Stellen schiebt<<alles hinaus (1 << 64ist0),>>lässt0oder-1übrig; ein negativer Zähler ist ein Fehler. - Index und Anzahl müssen ganz sein:
xs[4/2]undtake(xs, 2.0)gehen,xs[1.5]undtake(xs, 1.5)sind ein Fehler. - Riesige Exponenten:
**mit einem Int-Exponenten jenseits von 32 Bit ist ein Fehler, außer für die Basis 0, 1 und -1. - JSON hat nur Dezimalzahlen.
JSON.encodeschreibt einen Bruch mit endlicher Dezimaldarstellung exakt (1.5,0.25), Gleitkommazahlen mit allen nötigen Stellen und große Ganzzahlen vollständig. Für1/3gibt es keine exakte JSON-Zahl, das ist ein Fehler; schreibfloat(1/3), wenn eine Näherung reicht.JSON.parseliest Dezimalzahlen als Gleitkommazahlen. - Gleitkommazahlen haben die üblichen Grenzen: etwa 15 gültige Stellen,
Rundungsfehler, und
Math.sqrt(-1)ist ein Fehler. Eine exakte Zahl jenseits des Float-Bereichs (float(10 ** 400), auch in einer Rechnung mit einer Float) ist ein Fehler statt stillInf. - Literale dürfen höchstens 10 000 Stellen ergeben (siehe Literale).
Kurzreferenz#
- Int und Rat sind exakt und unbegrenzt, Float ist ungenau.
0.5ist ein Bruch, keine Gleitkommazahl.- Division liefert einen Bruch, auch
4 / 2. - Float entsteht nur durch
Math.*-Funktionen,**mit Bruch-Exponent,JSON.parseundfloat(...), und steckt an. **mit Int-Exponent bleibt exakt:(1/2) ** 2ist1/4.- Die Art folgt aus der Herkunft, nicht aus dem Wert.
Math.floor& Co. liefern einen Int.- Lange Bruch-Ketten, die nicht exakt sein müssen: den Faktor mit
float(...)umwandeln (v * float(0.95)).
