Fließkommaarithmetik

Aus C64-Wiki
(Weitergeleitet von Gleitkomma)
Zur Navigation springenZur Suche springen
disambig 30px.png Dieser Artikel beschreibt die interne Darstellung von und den Umgang mit Fließkommazahlen seitens des BASIC V2 des C64. Zu Fließkommazahlen aus Sicht der BASIC-Programmierung siehe Artikel Variable.

Fließkommaarithmetik ist eine Methode zur Darstellung und Handhabung eines weiten Bereichs von Realzahlen in binärer Form. Der eingebaute BASIC-Interpreter des C64 enthält eine Reihe von Unterprogrammen für die verschiedensten Berechnungen mit Fließkommazahlen (auch Gleitkomma- oder Gleitpunktzahlen genannt). Diese Routinen können auch aus vom Benutzer erstellten Maschinenspracheprogrammen heraus aufgerufen werden, um Berechnungen mit Realzahlen im Bereich von ±2.93873588·10−39 bis ±1.70141183·1038 durchzuführen.

Funktionsweise[Bearbeiten | Quelltext bearbeiten]

Eine Realzahl T im Fließkommaformat besteht aus einer Mantisse m und einem Exponenten E, welche so "ausgewählt" werden, dass T = m · 2E entspricht.

Die Mantisse ist normalisiert, d.h. eine Festkommabinärzahl im Bereich von 0,5 bis 1, so dass 0,5 ≤ m < 1 gilt, d.h. unmittelbar nach dem Komma steht stets eine 1, dahinter folgen mehrere (31 im Falle des vom C64-BASIC verwendeten Formats) binäre Nachkommastellen.

Der vorzeichenbehaftete Exponent ist eine 8-Bit-Ganzzahl in Exzesscodierung, um negative Exponenten darstellen zu können, die für Fließkommazahlen mit einem Betrag < 0,5 oder >= 1 benötigt werden. Der Wert 128 (entspricht dem Exzess bzw. engl. bias) entspricht dabei einem Exponenten von 0, größere Werte stellen einen entsprechend großen positiven und kleinere einen negativen Exponenten dar. Der Vorteil davon liegt in der einfachen Vergleichbarkeit zweier Werte, ohne eine Subtraktion durchführen zu müssen, auch wenn beim Rechnen mit Exponenten mitunter Korrekturen nötig sind.

Wert Bedeutung als Exponent Fließkommawert
$00 --- 0
$01 -127 2↑(-127) * Mantisse
. . .
$80 0 2↑0 * Mantisse
. . .
$FF +127 2↑127 * Mantisse

Ein separates Vorzeichenbit zeigt an, ob die aus Mantisse und Exponent resultierende Fließkommazahl positiv oder negativ ist. Damit wird der gesamte oben genannte Wertebereich abgedeckt, bis auf die 0: Da sich diese aufgrund der Wertebereichseinschränkungen der Mantisse mit o.g. Formel nicht darstellen lässt, signalisiert stattdessen ein Exponent mit dem Wert 0 (entspräche Exponent −128), dass der gesamte Fließkommaausdruck dem Wert 0 entspricht, unabhängig von der zugehörigen Mantisse.

Fließkommadarstellung auf dem C64[Bearbeiten | Quelltext bearbeiten]

Zu Fließkommazahlen aus Sicht der BASIC-Programmierung siehe Artikel Variable.

Grundsätzlich werden die Fließkommazahlen vom BASIC-Interpreter intern in zwei unterschiedlichen Formen abgelegt:

  1. In einer für Berechnungen praktikableren Form als Register bestehend aus 6 oder 7 Bytes.
  2. In Variablen in einer kompakten 5 Bytes umfassenden Form.

Registerdarstellung[Bearbeiten | Quelltext bearbeiten]

Zwei Bereiche in der Zeropage sind für das Rechnen mit Fließkommazahlen reserviert.

FAC[Bearbeiten | Quelltext bearbeiten]

FAC (Floating Point ACcumulator), gelegentlich auch als FAC1 bezeichnet, belegt sieben Bytes ab Adresse 97/$61.

  • Adresse 97/$61 enthält den Exponenten,
  • Adressen 98–101/$62–$65 enthalten die vier Bytes (32 Bit) der Mantisse,
  • Adresse 102/$66 enthält das Vorzeichen im Most Significant Bit: %0xxxxxxx (gelöschtes Vorzeichenbit) für positive und %1xxxxxxx (gesetztes Vorzeichenbit) für negative Zahlen,
  • Adresse 112/$70 enthält Rundungsbits der Mantisse bei Zwischenberechnungen.

ARG[Bearbeiten | Quelltext bearbeiten]

ARG (Floating Point ARGument), gelegentlich auch FAC2 oder AFAC (Alternate FAC) genannt, belegt sechs Bytes ab Adresse 105/$69. ARG ist im Wesentlichen wie FAC angelegt, beinhaltet aber kein Byte für Rundungsbits.

  • Adresse 105/$69 enthält den Exponenten,
  • Adressen 106–109/$6A–$6D werden von der Mantisse belegt,
  • Adresse 110/$6E enthält das Vorzeichen im Most Significant Bit: %0xxxxxxx (gelöschtes Vorzeichenbit) für positive und %1xxxxxxx (gesetztes Vorzeichenbit) für negative Zahlen.

Variablendarstellung[Bearbeiten | Quelltext bearbeiten]

Um den Speicherverbrauch zu reduzieren, werden Fließkommazahlen im restlichem Arbeitsspeicher auf 5 Bytes komprimiert: Da wegen der Normalisierung der Mantisse das führende Bit stets 1 ist, wird das dafür benötigte Bit von den ROM-Routinen, welche Zahlen aus FAC oder ARG in den Arbeitsspeicher kopieren, durch das Vorzeichenbit ersetzt. Die Routinen, welche in umgekehrter Richtung kopieren, lagern das Vorzeichenbit wieder aus und fügen das führende 1-Bit der Mantisse wieder hinzu.

Bezeichnung: exp sign/ma4 ma3 ma2 ma1
Byte: 0 1 2 3 4
Bit: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
E E E E E E E E S M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M

Typische Werte in der Variablendarstellung (das MSB der Mantisse, wäre an der Stelle, wo sich das Vorzeichen befindet, ist aber immer 1 und muss zur Bildung des Wertes 2^e * M gedanklich mit eingesetzt werden, es entspricht dem Dezimalwert 0,5):

Exponent
(hex.)
Mantisse
(hex.)
Wert
(dez.)
00 xx xx xx xx 0
(Mantisse egal)
01 00 00 00 00 2.93873588E-39
(betragsmäßig kleinster Wert)
80 00 00 00 00 0.5
81 00 00 00 00 1
81 80 00 00 00 -1
FF 7F FF FF FF 1.70141183e+38
(betragsmäßig größter positiver Wert)
FF FF FF FF FF -1.70141183e+38
(betragsmäßig größter negativer Wert)

Umrechnungsbeispiel[Bearbeiten | Quelltext bearbeiten]

  • Exponent: exp-128
  • Mantisse: (m4 >= 128 ? -1 : +1) * ((m4 | 0x80) >> 8 + m3 >> 16 + m2 >> 24 + m1 >> 32)
    als Ausdruck in der Syntax der Programmiersprache C, wobei "x >> y" as x*2↑(-y) zu verstehen ist (Bit-Schiebeoperation nach rechts).
     exp       m4       m3       m2       m1
      98       35       44       7A       00      - Hexadezimaldarstellung
     152       53       68      122        0      - Dezimaldarstellung
10011000 00110101 01000100 01111010 00000000      - Binärdarstellung
         ^sign bit

In diesem Fall:
Exponent = 152 - 128 = 24                                      ; dez
Mantisse = 0.10110101010001000111101000000000                  ; bin
Mantisse = +1 * (53 >> 8 + 68 >> 16 + 122 >> 24 + 0 >> 32)     ; dez
         = 1 * 2^-1 + 0 * 2^-2 + 1 * 2^-3 + 1 * 2^-4 + 0 * 2^-5 + ...
         = 0.70807611942291259765

Die Zahl ist also
0.70807611942291259765 * 2^24 = 11879546.0


Verwendung in Basic[Bearbeiten | Quelltext bearbeiten]

In sämtlichen CBM-Basic-Varianten werden Ausdrücke grundsätzlich in Fließkommaarithmetik durchgeführt, selbst dann, wenn nur einfachere Datentypen, wie etwa Ganzzahlvariablen beteiligt sind.

Abgesehen von den üblichen Operatoren gibt es folgende besondere Funktionen in Bezug auf Fließkommazahlen bzw. deren Eigenschaften:

  • ABS - den Betrag einer Fließkommazahl ermitteln (etwaiges negatives Vorzeichen entfernen)
  • INT - die nächstkleinere Ganzzahl ermitteln (Nachkommastellen entfernen).
  • SGN - das Vorzeichen ermitteln
  • STR$ - eine Fließkommazahl in eine Zeichenkette verwandeln
  • VAL - eine Zeichenkette in eine Fließkommazahl wandeln

Nutzung von Fließkommaroutinen[Bearbeiten | Quelltext bearbeiten]

So wie praktisch alle arithmetischen Maschinensprachebefehle den Akkumulator der CPU nutzen, stellen FAC und ARG die Dreh- und Angelpunkte für Fließkommaberechnungen dar: Die zu verarbeitenden Zahlen werden in FAC oder ARG kopiert und von den per JSR aufrufbaren Fließkommaroutinen im ROM gemäß deren Funktion manipuliert. Das Ergebnis der Operation steht anschließend im FAC bereit.

Falls Fließkommazahlen im Arbeitsspeicher für die Operation herangezogen oder aus FAC oder ARG dorthin kopiert werden, ist deren Adresse an die ROM-Routinen per A-, X- und/oder Y-Register zu übergeben, wobei die genaue Verwendung der Register je nach Funktion variiert. Auch muss man bei den meisten Rechenoperationen damit rechnen, dass FAC und ARG überschrieben werden.

Verschiebe-/Kopierroutinen[Bearbeiten | Quelltext bearbeiten]

Name Adresse
dez.
Adresse
hex.
Beschreibung
CONUPK 47756 BA8C ARG mit Zahl aus dem Speicher füllen (A=Adr.LB, Y=Adr.HB). Vergleicht danach noch als Vorbereitung für nachfolgende Operationen die Vorzeichen von ARG und FAC und schreibt das Ergebnis nach Adresse $6F ($00: Vorzeichen gleich, $80: Vorzeichen ungleich), und lädt den Exponenten von FAC nach A (d.h. setzt Zero Flag genau dann, wenn FAC gleich null). Die Routinen FADDT, FDIVT, FMULTT und FPWRT setzen diese Vorbereitung voraus.
MOVAF 48124 BBFC Zahl in ARG nach FAC kopieren
MOVFA 48140 BC0C Zahl in FAC nach ARG kopieren
MOVFM 48034 BBA2 FAC mit Zahl aus dem Speicher füllen (A=Adr.LB, Y=Adr.HB)
MOVMF 48084 BBD4 Zahl aus FAC im Speicher ablegen (X=Adr.LB, Y=Adr.HB)

Formatumwandlungen[Bearbeiten | Quelltext bearbeiten]

Name Adresse
dez.
Adresse
hex.
Beschreibung
FACINX 45482 B1AA Zahl in FAC zu 16-Bit-Ganzzahl mit Vorzeichen wandeln (Y=LB, A=HB).
FIN 48371 BCF3 Nullterminierten PETSCII-String zu Fließkommazahl in FAC wandeln (Stringadresse in $7A/$7B). Erwartet erstes Zeichen im Akku und gelöschtes Carry-Flag, z.B. durch Aufruf von CHRGOT (JSR $0079).
STRVAL 47029 B7B5 PETSCII-String zu Fließkommazahl in FAC wandeln (String-Adresse in $22/$23, String-Länge im Akku).
FOUT 48605 BDDD Zahl in FAC zu nullterminiertem PETSCII-String wandeln (ab $0100 ff., Adresse auch in A, Y). Direkte Ausgabe des FAC-Inhalts als String ist auch über $AABC/43708 möglich.
GIVAYF 45969 B391 16-Bit-Ganzzahl mit Vorzeichen zu Fließkommazahl in FAC wandeln (Y=LB, A=HB).
QINT 48283 BC9B Zahl in FAC in 32-Bit-Ganzzahl mit Vorzeichen wandeln ($62-$65, Big-Endian-Reihenfolge).

Rechenroutinen[Bearbeiten | Quelltext bearbeiten]

Name Adresse
dez.
Adresse
hex.
Beschreibung
ABS (ROM-Routine) 48216 BC58 Funktion ABS auf FAC anwenden
ATN (ROM-Routine) 58126 E30E Funktion ATN auf FAC anwenden
COS (ROM-Routine) 57956 E264 Funktion COS auf FAC anwenden
MUL10 47842 BAE2 Zahl in FAC mit 10 multiplizieren
DIV10 47870 BAFE Zahl in FAC durch 10 teilen. Vorzeichen wird ignoriert, das Ergebnis ist stets positiv.
EXP (ROM-Routine) 49133 BFED Funktion EXP auf FAC anwenden
FADD 47207 B867 Addiert Zahl in RAM zu FAC (A=Adr.LB, Y=Adr.HB)
FADDH 47177 B849 Addiert 0.5 zu FAC - nützlich zum Vorbereiten einer nachfolgenden Rundung
FADDT 47210 B86A Addiert ARG zu FAC (Voraussetzungen beachten!)
FDIV 47887 BB0F Teilt Zahl in RAM durch FAC (A=Adr.LB, Y=Adr.HB)
FDIVT 47890 BB12 Teilt ARG durch FAC (Voraussetzungen beachten!)
FINLOG 48510 BD7E Akkumulator als vorzeichenbehaftete Ganzzahl zu FAC addieren
FMULT 47656 BA28 Multipliziert FAC und Zahl im RAM (A=Adr.LB, Y=Adr.HB)
FMULTT 47664 BA30 Multipliziert FAC mit ARG, wie FMULT, aber ohne Einlesen der Zahl nach ARG aus dem RAM - nützlich, wenn der 2. Faktor bereits in ARG präsent ist (Voraussetzungen beachten!)
FPWR 49016 BF78 Potenziert ARG mit Zahl aus RAM (A=Adr.LB, Y=Adr.HB)
FPWRT 49019 BF7B Potenziert ARG mit FAC (Voraussetzungen beachten!)
FSUB 47184 B850 Subtrahiert FAC von Zahl in RAM (A=Adr.LB, Y=Adr.HB)
FSUBT 47187 B853 Subtrahiert FAC von ARG
INT (ROM-Routine) 48332 BCCC Funktion INT auf FAC anwenden
LOG (ROM-Routine) 47594 B9EA Funktion LOG auf FAC anwenden
NEGOP 49076 BFB4 Ändert das Vorzeichen einer Zahl !=0 in FAC
POLY 57433 E059 Polynomauswertung für FAC
POLYX 57411 E043 Polynomauswertung nur mit ungeraden Exponenten für FAC
ROUND 48155 BC1B FAC runden (Rundungsbyte in Mantisse einarbeiten)
SGN (ROM-Routine) 48185 BC39 Funktion SGN auf FAC anwenden
SIN (ROM-Routine) 57963 E26B Funktion SIN auf FAC anwenden
SQR (ROM-Routine) 49009 BF71 Funktion SQR auf FAC anwenden
TAN (ROM-Routine) 58036 E2B4 Funktion TAN auf FAC anwenden

Vergleichsoperationen[Bearbeiten | Quelltext bearbeiten]

Name Adresse
dez.
Adresse
hex.
Beschreibung
FCOMP 48219 BC5B Vergleicht FAC mit Zahl im Speicher (A=Adr.LB, Y=Adr.HB). Ergebnis in Statusregister und Akku:
A=0: FAC=RAM,
A=1: FAC>RAM,
A=-1(255): FAC<RAM
SIGN 48171 BC2B Setzt Negative- und Zero-Flags im Statusregister entsprechend dem Vorzeichen und Betrag der Zahl in FAC

Weblinks[Bearbeiten | Quelltext bearbeiten]

WP-W11.png Wikipedia: Gleitkommazahl
WP-W11.png Wikipedia: Exzesscode