Eine Gleitkommazahl ist eine Zahl mit einem Nachkommateil, wie zum Beispiel -1.23 oder 3.1415.
Um eine solche Zahl als Bytefolge darzustellen, verwendet x86-64 den IEEE-754-Standard. Die beiden häufigsten Formate in diesem Standard sind Gleitkommazahlen mit einfacher Genauigkeit und mit doppelter Genauigkeit. x86-64 unterstützt beide.
Eine Gleitkommazahl mit einfacher Genauigkeit ist 32 Bit breit, eine mit doppelter Genauigkeit 64 Bit.
In x86-64 gibt es 16 dedizierte Register für Gleitkommaarithmetik, benannt von xmm0 bis xmm15.
Diese Register sind mindestens 128 Bit breit, doch neuere Prozessoren erweitern dies auf 256 Bit oder 512 Bit. In der Praxis nutzen die meisten Gleitkommazahlen jedoch nur die untersten 32 Bit oder 64 Bit, je nach gewünschter Genauigkeit.
Es gibt eine Familie von Instruktionen, um zwischen Zahlenformaten umzuwandeln: cvtxx2yy.
Sowohl xx als auch yy sind keine Literale, sondern Platzhalter:
xx ist ein Platzhalter für den Typ des Quelloperanden.yy ist ein Platzhalter für den Typ des Zieloperanden.Dieser Platzhalter kann viele Formen annehmen, darunter:
| Platzhalter | Bedeutung |
|---|---|
si |
vorzeichenbehaftete Ganzzahl |
ss |
32-Bit-Gleitkommazahl |
sd |
64-Bit-Gleitkommazahl |
Zum Beispiel:
cvtsi2ss xmm0, eax ; converts a 32-bit signed integer to a single-precision floating-point
cvtsd2si rax, xmm1 ; converts a double-precision floating-point to a 64-bit signed integer
Beachte, dass es möglich ist, eine 32-Bit-Ganzzahl in eine 64-Bit-Gleitkommazahl oder eine 64-Bit-Ganzzahl in eine 32-Bit-Gleitkommazahl umzuwandeln.
Ebenso ist es möglich, eine 32-Bit-Gleitkommazahl in eine 64-Bit-Ganzzahl oder eine 64-Bit-Gleitkommazahl in eine 32-Bit-Ganzzahl umzuwandeln.
Mit anderen Worten: Die Größe beider Operanden in einem cvtxx2yy muss nicht gleich sein:
cvtsi2sd xmm0, eax ; converts a 32-bit signed integer to a double-precision floating-point
cvtsi2ss xmm0, rax ; converts a 64-bit signed integer to a single-precision floating-point
cvtss2si rax, xmm0 ; converts a single-precision floating-point to a 64-bit signed integer
cvtsd2si eax, xmm0 ; converts a double-precision floating-point to a 32-bit signed integer
Wenn die Gleitkommazahl nicht exakt als Ganzzahl ausgedrückt werden kann, rundet cvtxx2si die Zahl entsprechend der Rundungssteuerung in einem speziellen Register namens mxcsr.
Das kann ein Runden auf den nächsten Wert, ein Abrunden, ein Aufrunden oder ein Abschneiden sein.
Beim Funktionsaufruf gibt es keinen garantierten Standardwert.
Beachte, dass ein exaktes Ergebnis nicht gerundet wird und daher von der Rundungssteuerung nicht betroffen ist.
Zum Beispiel würde 42.0 immer in 42 umgewandelt.
Es gibt eine ähnliche Instruktionsfamilie mit dem Format cvttxx2si (mit einem zusätzlichen t), die das Ergebnis immer abschneidet.
Die Instruktionsfamilie roundxx wandelt eine Gleitkommazahl im Quelloperanden in eine gerundete Gleitkommazahl um, die im Zieloperanden gespeichert wird.
Hier ist xx wie üblich ein Platzhalter für den Typ der Operanden: ss für Gleitkommazahlen mit einfacher Genauigkeit und sd für solche mit doppelter Genauigkeit.
Diese Instruktionen nehmen eine konstante Zahl als dritten Operanden. Diese Zahl gibt den Rundungsmodus an:
| Zahl | Modus |
|---|---|
| 0 | Nächster Wert |
| 1 | Abrunden (nach unten) |
| 2 | Aufrunden (nach oben) |
| 3 | Abschneiden |
Zum Beispiel:
roundss xmm0, xmm1, 1 ; this rounds down (takes the floor) a 32-bit floating-point in xmm1 and stores it in xmm0
roundsd xmm2, xmm2, 2 ; this rounds up (takes the ceil) a 64-bit floating-point in xmm2 and stores it in xmm2
Da diese Instruktionen ein exaktes Ergebnis liefern, können sie zur Vorbereitung auf ein cvtxx2si verwendet werden.
Viele Operationen mit Gleitkommazahlen verwenden Instruktionen mit einem ähnlichen Namen wie ihr Gegenstück für Ganzzahlen, ergänzt um den nötigen Platzhalter (ss oder sd) am Ende.
Diese Instruktionen haben zwei Operanden, mit der üblichen Semantik, auch die, die eine Multiplikation oder Division durchführen:
addss xmm1, xmm2 ; xmm1 = xmm1 + xmm2 (all 32-bit floating-point)
mulsd xmm4, xmm5 ; xmm4 = xmm4 * xmm5 (all 64-bit floating-point)
divss xmm0, xmm3 ; xmm0 = xmm0 / xmm3 (all 32-bit floating-point)
Die Instruktionen, um von einem XMM-Register in ein anderes zu verschieben, sind movss oder movsd:
movss xmm1, xmm0 ; this copies a 32-bit floating-point from xmm0 to xmm1
movsd xmm6, xmm4 ; this copies a 64-bit floating-point from xmm4 to xmm6
Es ist nicht möglich, einen Immediate-Wert (eine konstante vorzeichenbehaftete Ganzzahl) direkt in ein xmm-Register zu verschieben.
Diese Zahl muss zuerst in ein Allzweckregister verschoben und dann mit cvtsi2xx umgewandelt werden:
mov rax, 10 ; rax is a 64-bit integer with value 10
cvtsi2sd xmm0, rax ; xmm0 is a 64-bit floating-point with value 10.0
Es gibt zwei Gruppen von Instruktionen, um zwei Gleitkommazahlen zu vergleichen.
Sie haben die Form comixx und ucomixx, wobei xx entweder ss oder sd ist.
Beide setzen wie üblich Flags in rflags, mit derselben Semantik wie cmp.
Der Unterschied zwischen ihnen besteht darin, dass ucomixx das parity flag (PF) setzt, wenn eine der beiden Gleitkommazahlen den Wert NaN hat, während comixx in diesem Fall eine Ausnahme auslöst.
Der Vergleich zwischen Gleitkommawerten ist vorzeichenlos:
ucomisd xmm0, xmm1
ja .greater ; 'ja' must be used for "greater", not 'jg'
jb .lesser ; 'jb' must be used for "lesser", not 'jl'
In der System-V-ABI werden die ersten acht Gleitkomma-Argumente der Reihe nach in xmm0 bis xmm7 an Funktionen übergeben.
Gleitkommawerte werden in xmm0 und, falls nötig, in xmm1 von Funktionen zurückgegeben.
Alle xmm-Register stehen frei zur Verfügung, keines von ihnen ist für den Aufrufer reserviert.
Gleitkommawerte können im Speicher mit derselben Semantik wie Ganzzahlen derselben Größe definiert werden. NASM (The Netwide Assembler, der in diesem Track verwendete Assembler) erlaubt auch die Deklaration von Gleitkommawerten in wissenschaftlicher Notation.
section .data
pi dq 3.141592653589793 ; this is a double-precision floating-point value
exp_num dd 1.e5 ; this is a single precision floating-point value equal to 1 * 10⁵
exp_num2 dd 1.e+5 ; this is a single precision floating-point value equal to 1 * 10⁵
frac dq 1.e-10 ; this is a double precision floating-point value equal to 1 * 10⁻¹⁰, i.e., 0.0000000001
In dieser Übung schreibst du Code, der einem Freelancer bei der Kommunikation mit einem Projektmanager hilft. Deine Aufgabe ist es, ein paar Hilfsfunktionen bereitzustellen, mit denen du schnell Tages- und Monatssätze berechnen kannst, optional mit einem gegebenen Rabatt.
Zuerst legen wir ein paar Regeln zwischen dem Freelancer und dem Projektmanager fest:
Manchmal bietet der Freelancer an, einen Rabatt auf seinen Tagessatz zu gewähren (zum Beispiel für seine treuesten Kunden oder gemeinnützige Kunden).
Rabatte werden als Bruchzahlen modelliert, die Prozentwerte darstellen, zum Beispiel 25.0 (25 %).
Das sind die Suffixe für Gleitkomma-Befehle:
| Suffix | Operandengröße |
|---|---|
ss |
32 Bit |
sd |
64 Bit |
Das sind einige der Befehle, die in diesem Konzept erwähnt werden:
| Befehl | Beschreibung |
|---|---|
| movss/movsd a, b | kopiert b nach a |
| addss/addsd a, b | a = a + b |
| subss/subsd a, b | a = a - b |
| mulss/mulsd a, b | a = a * b |
| divss/divsd a, b | a = a / b |
| cvtsi2ss/cvtsi2sd a, b | wandelt die Ganzzahl b in die Gleitkommazahl a um |
| roundss/roundsd a, b, c | rundet die Gleitkommazahl b gemäß c und speichert das Ergebnis in a |
| cvtss2si/cvtsd2si a, b | wandelt die Gleitkommazahl b in die Ganzzahl a um |
| comiss/comisd a, b | vergleicht a und b, löst eine Ausnahme aus, wenn NaN
|
| ucomiss/ucomisd a, b | vergleicht a und b, setzt PF, wenn NaN
|
Das sind die Rundungssteuerzahlen in roundss und roundsd:
| Zahl | Modus |
|---|---|
| 0 | Nächster Wert |
| 1 | Abrunden (nach unten) |
| 2 | Aufrunden (nach oben) |
| 3 | Abschneiden |
Implementiere eine Funktion namens daily_rate, die den Tagessatz anhand eines als Parameter übergebenen Stundensatzes berechnet, einer 64-Bit-Gleitkommazahl.
Der Vertrag legt fest, dass ein Tag 8 abrechenbare Stunden hat.
daily_rate(60)
// => 480.0
Der zurückgegebene Tagessatz sollte eine 64-Bit-Gleitkommazahl sein.
Implementiere eine Funktion apply_discount, die den Preis nach einem Rabatt berechnet.
Sie sollte zwei Parameter akzeptieren: den ursprünglichen Preis und den Rabattsatz in Prozent, beide als 64-Bit-Gleitkommazahl.
apply_discount(150.0, 10.0)
// => 135.0
Der zurückgegebene Wert sollte immer eine 64-Bit-Gleitkommazahl sein.
Implementiere eine Funktion monthly_rate, die den rabattierten Monatssatz berechnet.
Sie nimmt zwei Parameter, einen Stundensatz und den Rabatt in Prozent, beide als 64-Bit-Gleitkommazahl.
monthly_rate(77.0, 10.5)
// => 12130
Der zurückgegebene Monatssatz sollte auf die nächste 64-Bit-Ganzzahl aufgerundet werden (nimm also die Obergrenze).
Implementiere eine Funktion days_in_budget, die folgende Parameter nimmt:
Diese Funktion sollte die Anzahl der vollständigen Arbeitstage zurückgeben, die das Budget abdeckt.
days_in_budget(20000, 80, 11.0)
// => 35
Die zurückgegebene Anzahl von Tagen sollte auf die nächste 32-Bit-Ganzzahl abgerundet werden (nimm also die Untergrenze).
Melde dich bei Exercism an, um x86-64 Assembly mit 22 Konzepte130 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.