Un numero in virgola mobile è un numero che può avere una parte frazionaria, come -1.23 o 3.1415.
Per rappresentare un tale numero come una sequenza di byte, x86-64 usa lo standard IEEE-754. I due formati più comuni in questo standard sono i numeri in virgola mobile a precisione singola e a precisione doppia. x86-64 supporta entrambi.
Un numero in virgola mobile a precisione singola è largo 32 bit, mentre uno a precisione doppia è largo 64 bit.
In x86-64, ci sono 16 registri dedicati per eseguire aritmetica in virgola mobile, chiamati xmm0 fino a xmm15.
Quei registri sono larghi almeno 128 bit, ma i processori recenti aumentano questa larghezza a 256 bit o 512 bit. Tuttavia, in pratica, la maggior parte dei numeri in virgola mobile usa solo i 32 bit o i 64 bit più bassi, a seconda della precisione desiderata.
Esiste una famiglia di istruzioni per convertire tra formati numerici: cvtxx2yy.
Sia xx che yy non sono letterali, ma segnaposto:
xx è un segnaposto per il tipo dell'operando sorgente.yy è un segnaposto per il tipo dell'operando destinazione.Questo segnaposto può assumere molte forme, tra cui:
| segnaposto | significato |
|---|---|
si |
intero con segno |
ss |
numero in virgola mobile a 32 bit |
sd |
numero in virgola mobile a 64 bit |
Per esempio:
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
Nota che è possibile convertire un intero a 32 bit in un numero in virgola mobile a 64 bit o un intero a 64 bit in un numero in virgola mobile a 32 bit.
Allo stesso modo, è anche possibile convertire un numero in virgola mobile a 32 bit in un intero a 64 bit o un numero in virgola mobile a 64 bit in un intero a 32 bit.
In altre parole, le dimensioni di entrambi gli operandi in un cvtxx2yy non devono essere le stesse:
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
Quando il numero in virgola mobile non può essere espresso esattamente come un intero, cvtxx2si arrotonda il numero secondo il controllo di arrotondamento in un registro speciale chiamato mxcsr.
Questo può essere un arrotondamento al più vicino, verso il basso, verso l'alto o una troncatura.
Non c'è un valore predefinito garantito all'ingresso della funzione.
Nota che un risultato esatto non viene arrotondato e, quindi, non è influenzato dal controllo di arrotondamento.
Ad esempio, 42.0 verrebbe sempre convertito in 42.
Esiste una famiglia simile di istruzioni con il formato cvttxx2si (con una t in più) che tronca sempre il risultato.
La famiglia di istruzioni roundxx converte un numero in virgola mobile nell'operando sorgente in un numero in virgola mobile arrotondato memorizzato nell'operando destinazione.
Qui, xx è un segnaposto per il tipo degli operandi, come al solito: ss per i numeri in virgola mobile a precisione singola e sd per quelli a precisione doppia.
Queste istruzioni prendono un numero costante come terzo operando. Questo numero indica la modalità di arrotondamento:
| Numero | Modalità |
|---|---|
| 0 | Al più vicino |
| 1 | Arrotonda verso il basso |
| 2 | Arrotonda verso l'alto |
| 3 | Tronca |
Per esempio:
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
Poiché queste istruzioni producono un risultato esatto, possono essere usate in preparazione a un cvtxx2si.
Molte operazioni con numeri in virgola mobile usano istruzioni con un nome simile a quello del loro equivalente per gli interi, ma aggiungendo il segnaposto necessario (ss o sd) alla fine.
Queste istruzioni sono a due operandi, con la solita semantica, anche quelle che eseguono moltiplicazione o divisione:
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)
Le istruzioni per spostare da un registro XMM a un altro sono movss o 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
Non è possibile spostare un immediato (un intero costante con segno) direttamente in un registro xmm.
Questo numero deve prima essere spostato in un registro di uso generale e poi convertito usando cvtsi2xx:
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
Esistono due gruppi di istruzioni per fare un confronto tra due numeri in virgola mobile.
Prendono la forma comixx e ucomixx, dove xx è ss o sd.
Entrambi impostano i flag in rflags come al solito, seguendo la stessa semantica di cmp.
La differenza tra loro è che ucomixx imposta il parity flag (PF) se uno dei due numeri in virgola mobile ha un valore NaN, mentre comixx solleva un'eccezione in questo caso.
Il confronto tra valori in virgola mobile è senza segno:
ucomisd xmm0, xmm1
ja .greater ; 'ja' must be used for "greater", not 'jg'
jb .lesser ; 'jb' must be used for "lesser", not 'jl'
Nella System V ABI, i primi otto argomenti in virgola mobile vengono passati alle funzioni in ordine, da xmm0 a xmm7.
I valori in virgola mobile vengono restituiti dalle funzioni in xmm0 e, se necessario, in xmm1.
Tutti i registri xmm sono liberamente utilizzabili, nessuno di essi è riservato al chiamante.
I valori in virgola mobile possono essere definiti in memoria seguendo la stessa semantica degli interi della stessa dimensione. NASM (The Netwide Assembler, l'assembler usato da questo track) permette anche la dichiarazione di valori in virgola mobile in notazione scientifica.
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 questo esercizio scriverai del codice per aiutare un freelancer a comunicare con un project manager. Il tuo compito è fornire alcune funzioni di utilità per calcolare rapidamente le tariffe giornaliere e mensili, facoltativamente con uno sconto dato.
Stabiliamo innanzitutto alcune regole tra il freelancer ed il project manager:
A volte il freelancer propone di applicare uno sconto sulla propria tariffa giornaliera (per esempio per i clienti più fedeli o per i clienti senza scopo di lucro).
Gli sconti sono rappresentati come numeri frazionari che esprimono percentuali, per esempio 25.0 (25%).
Questi sono i suffissi delle istruzioni in virgola mobile:
| Suffisso | Dimensione dell'operando |
|---|---|
ss |
32 bit |
sd |
64 bit |
Queste sono alcune delle istruzioni menzionate in questo concetto:
| Istruzione | Descrizione |
|---|---|
| movss/movsd a, b | copia b in 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 | converte l'intero b nel numero in virgola mobile a |
| roundss/roundsd a, b, c | arrotonda il numero in virgola mobile b secondo c e lo memorizza in a |
| cvtss2si/cvtsd2si a, b | converte il numero in virgola mobile b nell'intero a |
| comiss/comisd a, b | fa un confronto tra a e b, solleva un'eccezione se NaN
|
| ucomiss/ucomisd a, b | fa un confronto tra a e b, imposta PF se NaN
|
Questi sono i numeri di controllo dell'arrotondamento in roundss e roundsd:
| Numero | Modalità |
|---|---|
| 0 | Al più vicino |
| 1 | Per difetto (down) |
| 2 | Per eccesso (up) |
| 3 | Troncamento |
Implementa una funzione chiamata daily_rate per calcolare la tariffa giornaliera data come parametro una tariffa oraria, come numero in virgola mobile a 64 bit.
Il contratto stabilisce che un giorno ha 8 ore fatturabili.
daily_rate(60)
// => 480.0
La tariffa giornaliera restituita deve essere un numero in virgola mobile a 64 bit.
Implementa una funzione apply_discount per calcolare il prezzo dopo uno sconto.
Deve accettare due parametri: il prezzo originale e la percentuale di sconto, entrambi come numeri in virgola mobile a 64 bit.
apply_discount(150.0, 10.0)
// => 135.0
Il valore restituito deve essere sempre un numero in virgola mobile a 64 bit.
Implementa una funzione monthly_rate per calcolare la tariffa mensile scontata.
Prende due parametri, una tariffa oraria e lo sconto in percentuale, entrambi come numeri in virgola mobile a 64 bit.
monthly_rate(77.0, 10.5)
// => 12130
La tariffa mensile restituita deve essere arrotondata per eccesso all'intero a 64 bit più vicino.
Implementa una funzione days_in_budget che prende come parametri:
Questa funzione deve restituire il numero di giornate lavorative complete coperte dal budget.
days_in_budget(20000, 80, 11.0)
// => 35
Il numero di giorni restituito deve essere arrotondato per difetto all'intero a 32 bit più vicino.
Iscriviti a Exercism per imparare e padroneggiare x86-64 Assembly con 22 concetti130 esercizi e il mentoring di persone reali, tutto gratis.