Tema 5: Coma flotant

5.1 Representació en coma flotant

5.1.1 Notació científica normalitzada

La notació científica normalitzada és un sistema de representació de nombres reals que separa clarament la magnitud del nombre (els seus dígits significatius) de la seva escala (la posició de la coma). Un nombre \(v\) s’escriu en la forma:

\[v = m \times 10^e\]

on l’exponent \(e\) és un enter i la mantissa (significand) \(m\) satisfà \(1 \leq |m| < 10\), amb almenys un dígit fraccionari. Aquesta restricció s’anomena condició de normalització i garanteix una representació única per a cada nombre no nul.

La notació científica normalitzada presenta dos avantatges respecte de la representació decimal ordinària. D’una banda, és més compacta: \(2{,}34 \times 10^8\) és més llegible que \(234\,000\,000\). D’altra banda, fa explícit el nombre de dígits significatius: \(2{,}34 \times 10^8\) indica clarament tres dígits significatius, mentre que \(234\,000\,000\) és ambigu (poden ser tres, o bé el nombre pot ser exacte fins a les unitats). El terme coma flotant fa referència precisament a aquest mecanisme: la coma «es desplaça» cap a l’esquerra o cap a la dreta en funció del valor de l’exponent.

En dispositius electrònics, els tres caràcters ×10 es substitueixen habitualment per la lletra E, que separa mantissa i exponent: \(2{,}34\text{E}8\).

Exemple 5.1: Conversió a notació científica normalitzada
Nombre Notació científica normalitzada
\(234\,000\,000\) \(2{,}34 \times 10^{8}\)
\(0{,}000\,468\) \(4{,}68 \times 10^{-4}\)
\(-0{,}0234 \times 10^{10}\) \(-2{,}34 \times 10^{8}\)

5.1.2 Representació binària

Els nombres en coma flotant en base 2 segueixen el mateix principi que la notació científica normalitzada, amb la base 2 en lloc de la base 10. La condició de normalització imposa que la part entera de la mantissa sigui exactament 1, ja que en base 2 l’únic dígit no nul és l’1:

\[v = \pm 1{,}\underbrace{b_{-1}b_{-2}\ldots b_{-(p-1)}}_{\text{fracció }F} \times 2^e\]

on l’exponent \(e\) és un enter, \(F\) és la part fraccionària de la mantissa i \(p\) és la precisió, el nombre de bits significatius de la mantissa.

Essencial 5.1: El bit ocult

Com que la condició de normalització garanteix que el bit a l’esquerra de la coma és sempre 1, aquest bit no s’emmagatzema a la codificació: és el bit ocult (hidden bit). El maquinari el torna a afegir implícitament en qualsevol operació.

Cal tenir-lo en compte en tots els càlculs: si el camp de fracció \(F\) té \(p-1\) bits emmagatzemats, la mantissa completa té \(p\) bits significatius: els \(p-1\) bits de \(F\) més el bit ocult.

Els únics casos en què el bit ocult no s’aplica són els nombres subnormals i el zero, a més de les codificacions especials \(\pm\infty\) i not a number (NaN), que no representen cap mantissa (Secció 5.1.6).

5.1.3 L’estàndard IEEE 754

Fins als anys 80, cada fabricant de processadors adoptava el seu propi format de coma flotant, amb decisions diverses sobre el nombre de bits de mantissa i exponent, i fins i tot sobre la base de la potència. La incompatibilitat entre sistemes dificultava l’intercanvi de dades i la portabilitat del programari. El 1985, l’Institute of Electrical and Electronics Engineers (IEEE) va publicar l’estàndard IEEE 754, que no sols defineix el format de representació sinó també les operacions aritmètiques, l’arrodoniment i el tractament de les excepcions. L’estàndard s’ha revisat i ampliat posteriorment (la darrera revisió és de 2019).

RISC-V 5.1: RV32F ISA — Formats IEEE 754 de precisió simple i doble
Precisió simple Precisió doble (extensió D; no s’estudia a EC)
Tipus C float double
Mida total 32 bits 64 bits
Signe (S) 1 bit 1 bit
Exponent (E) 8 bits 11 bits
Fracció (F) 23 bits 52 bits
Precisió (amb bit ocult) 24 bits 53 bits
Biaix de l’exponent 127 1023
Rang de l’exponent \(e\) \([-126,\ +127]\) \([-1022,\ +1023]\)
EC 5.1

A EC, s’usa exclusivament el format de precisió simple. Totes les operacions, exemples i problemes fan referència a aquest format llevat que s’indiqui explícitament el contrari.

Els tres camps de bits del format de precisió simple s’organitzen de la manera següent (Figura 5.1):

\[\underbrace{S}_{1}\;\underbrace{E_7 E_6 \ldots E_0}_{8}\;\underbrace{F_{22} F_{21} \ldots F_0}_{23}\]

S(31), E(30–23), F(22–0).

S(31), E(30–23), F(22–0).

Figura 5.1: Disposició dels camps S, E i F en el format IEEE 754 de precisió simple (32 bits).

5.1.3.1 Signe (S)

El bit de signe \(S\) indica el signe del nombre: \(S = 0\) per a valors positius i \(S = 1\) per a valors negatius.

5.1.3.2 Fracció (F)

El camp de fracció \(F\) emmagatzema els \(p-1 = 23\) bits de la part fraccionària de la mantissa normalitzada. La mantissa completa s’obté afegint el bit ocult: \(1{,}F\).

5.1.3.3 Exponent (E)

L’exponent és un enter (pot ser negatiu), però no es codifica en Ca2. S’emmagatzema com a exponent esbiaixat (biased exponent), amb biaix 127, és a dir, en excés a 127 en el sentit de la Secció 1.5.2: el valor emmagatzemat \(E_u\) és el natural que satisfà

\[e = E_u - 127 \qquad \Longleftrightarrow \qquad E_u = e + 127 \tag{5.1}\]

on \(e\) és el valor enter de l’exponent. Per exemple, \(e = 0 \Rightarrow E_u = 127 = 0111\,1111_2\); \(e = 1 \Rightarrow E_u = 128 = 1000\,0000_2\).

Essencial 5.2: Per què l’exponent es codifica esbiaixat?

La representació esbiaixada (Secció 1.5.2 i EC 1.3) garanteix que l’ordre numèric dels valors explícits \(E_u\) (interpretats com a naturals) coincideix amb l’ordre dels exponents enters \(e\). Això permet comparar les magnituds de dos nombres en coma flotant amb el mateix circuit que compara naturals (si tots dos són positius, l’ordre coincideix amb el dels valors; si tots dos són negatius, és l’invers), amb dues condicions:

  1. Els bits de l’exponent ocupen la part alta del nombre, immediatament després del signe, i els de la fracció ocupen la part baixa. Així, a exponents iguals guanya el de fracció major; entre exponents diferents guanya el de l’exponent major.
  2. L’exponent es codifica esbiaixat (no en Ca2), de manera que la correspondència entre representacions i valors és monòtona creixent.

El valor complet representat per una codificació normalitzada és:

\[v = (-1)^S \times 1{,}F \times 2^{E_u - 127} \tag{5.2}\]

La Figura 5.2 mostra la correspondència entre l’exponent emmagatzemat \(E_u\) i el valor real \(e = E_u - 127\), i n’indica les zones reservades per a subnormals i codificacions especials.

Dues rectes. La de dalt és l'exponent emmagatzemat, de 0 a 255: el 0 és el dels subnormals, el 255 el de ±∞ i NaN, i els normals van de 1 a 254. La de sota és l'exponent real, de −126 (Emin) a 127 (Emax), amb el 0 al mig. Unes línies discontínues uneixen 1 amb −126, 127 amb 0 i 254 amb 127, i dues fletxes indiquen que es passa d'una recta a l'altra restant o sumant 127. A sota, els normals extrems: ±1,1754944e−38 (0x00800000 i 0x80800000), 1 (0x3F800000) i ±3,4028235e38 (0x7F7FFFFF i 0xFF7FFFFF).

Dues rectes. La de dalt és l'exponent emmagatzemat, de 0 a 255: el 0 és el dels subnormals, el 255 el de ±∞ i NaN, i els normals van de 1 a 254. La de sota és l'exponent real, de −126 (Emin) a 127 (Emax), amb el 0 al mig. Unes línies discontínues uneixen 1 amb −126, 127 amb 0 i 254 amb 127, i dues fletxes indiquen que es passa d'una recta a l'altra restant o sumant 127. A sota, els normals extrems: ±1,1754944e−38 (0x00800000 i 0x80800000), 1 (0x3F800000) i ±3,4028235e38 (0x7F7FFFFF i 0xFF7FFFFF).

Figura 5.2: Correspondència entre l’exponent emmagatzemat \(E_u\) (0–255) i l’exponent real \(e = E_u - 127\): \(E_u = 0\) reservat per a subnormals i \(\pm 0\); \(E_u = 255\) per a \(\pm\infty\) i NaN.
Exemple 5.2: Conversió a IEEE 754 de precisió simple

Codifica el nombre \(v = -1029{,}68\) en format IEEE 754 de precisió simple i calcula l’error de representació.

a) Part entera a binari (divisions successives):

\[1029 = 1000\,0000\,0101_2 \quad (11 \text{ bits})\]

b) Part fraccionària a binari (multiplicacions successives per 2 i extracció de la part entera de cada resultat com a bit de la representació binària de la part fraccionària):

\[0{,}68 \rightarrow 1{,}36 \rightarrow 0{,}72 \rightarrow 1{,}44 \rightarrow 0{,}88 \rightarrow 1{,}76 \rightarrow 1{,}52 \rightarrow 1{,}04 \rightarrow 0{,}08 \rightarrow \ldots\]

\[(0 \quad \rightarrow 1 \quad \rightarrow 0 \quad \rightarrow 1 \quad \rightarrow 0 \quad \rightarrow 1 \quad \rightarrow 1 \quad \rightarrow 1 \quad \rightarrow 0 \quad \rightarrow \ldots)\]

\[0{,}16 \rightarrow 0{,}32 \rightarrow 0{,}64 \rightarrow 1{,}28 \rightarrow 0{,}56 \rightarrow 1{,}12 \rightarrow 0{,}24 \rightarrow 0{,}48 \rightarrow 0{,}96 \rightarrow 1{,}92 \rightarrow \ldots\]

\[(0 \quad \rightarrow 0 \quad \rightarrow 0 \quad \rightarrow 1 \quad \rightarrow 0 \quad \rightarrow 1 \quad \rightarrow 0 \quad \rightarrow 0 \quad \rightarrow 0 \quad \rightarrow 1 \quad \rightarrow \ldots)\]

\(0{,}68 = 0{,}1010\,1110\,0001\,0100\,01\ldots_2\). Per tant:

\[v = -1000\,0000\,101{,}1010\,1110\,0001\,0100\,01\ldots_2\]

c) Normalitzar la mantissa:

\[v = -1{,}0000\,0001\,0110\,1011\,1000\,0101\,0001\ldots_2 \times 2^{10}\]

d) Arrodonir a 24 bits significatius (23 bits de fracció + bit ocult).

Els bits a descartar comencen per 10001...; com que el primer bit descartat és 1 i n’hi ha d’altres a la dreta, cal arrodonir al valor següent (sumar 1 al bit de menys pes conservat). El criteri general d’arrodoniment es formalitza a Secció 5.1.7; en aquest cas:

\[\begin{array}{r} 1{,}0000\,0001\,0110\,1011\,1000\,010\\ +\hspace{9.3em}1\\ \hline 1{,}0000\,0001\,0110\,1011\,1000\,011 \end{array}\]

e) Codificar l’exponent en forma esbiaixada (biaix 127):

\[E_u = 10 + 127 = 137 = 1000\,1001_2\]

f) Ajuntar S, E, F (descartant el bit ocult de la fracció):

\[V = \underbrace{1}_{S}\;\underbrace{1000\,1001}_{E}\;\underbrace{0000\,0001\,0110\,1011\,1000\,011}_{F}\]

\[V = 1100\,0100\,1000\,0000\,1011\,0101\,1100\,0011_2 = \texttt{0xC480B5C3}\]

Solució:

\[V = \texttt{0xC480B5C3}\]

g) Error de representació:

L’error de representació és la diferència entre el valor realment emmagatzemat i el valor exacte original. Com que el valor exacte té infinits bits en binari, és més senzill calcular aquesta diferència en decimal. Desfent la normalització de la mantissa arrodonida (coma 10 posicions a la dreta), el valor emmagatzemat (en valor absolut) és:

\[1{,}0000\,0001\,0110\,1011\,1000\,011_2 \times 2^{10} = 1000\,0000\,101{,}1010\,1110\,0001\,1_2\]

La part entera (\(1000\,0000\,101_2 = 1029\)) no ha canviat amb l’arrodoniment: l’error és, doncs, la diferència entre les parts fraccionàries. La part fraccionària emmagatzemada val:

\[0{,}1010\,1110\,0001\,1_2 = 1\,0101\,1100\,0011_2 \times 2^{-13} = \frac{5571}{8192}\]

\[\varepsilon = \left|\frac{5571}{8192} - 0{,}68\right| = \frac{5571 - 0{,}68 \times 8192}{8192} = \frac{0{,}44}{8192} \approx 5{,}371 \times 10^{-5}\]

Solució:

\[\varepsilon = 5{,}371 \times 10^{-5}\]

Exemple 5.3: Conversió des d’IEEE 754 de precisió simple a base 10

Converteix a decimal el nombre 0x45814140.

a) Descompondre en camps S, E, F:

\[\texttt{0x45814140} = 0\;|\;1000\,1011\;|\;0000\,0010\,1000\,0010\,1000\,000_2\]

b) Descodificar signe, exponent i mantissa:

  • \(S = 0\) (positiu).
  • \(E_u = 1000\,1011_2 = 139 \Rightarrow e = 139 - 127 = 12\).
  • Mantissa (amb bit ocult): \(1{,}0000\,0010\,1000\,0010\,1000\,000_2\).

\[v = +1{,}0000\,0010\,1000\,0010\,1000\,000_2 \times 2^{12}\]

c) Eliminar la potència (desplaçar la coma 12 posicions a la dreta):

\[v = 1\,0000\,0010\,1000{,}0010\,1000\,000_2\]

d) Convertir la part entera:

\[1\,0000\,0010\,1000_2 = 2^{12} + 2^5 + 2^3 = 4096 + 32 + 8 = 4136\]

e) Convertir la part fraccionària:

\[0{,}0010\,1000\,000_2 = 101_2 \times 2^{-5} = 5 / 32 = 0{,}15625\]

Solució:

\[v = 4136{,}15625\]

5.1.4 Rang i precisió

El rang de la representació és l’interval \([-v_{max},\ +v_{max}]\), on \(v_{max}\) és el nombre positiu més gran representable. S’obté amb la màxima fracció (tots els bits a 1) i el màxim exponent (\(e = 127\) en precisió simple):

\[v_{max} = (2 - 2^{-23}) \times 2^{127} \approx 3{,}4 \times 10^{38}\]

Si el resultat d’una operació té un exponent \(e > 127\), no és representable i es produeix una excepció de desbordament (overflow).

La precisió d’una representació és el seu nombre de bits significatius. En precisió simple, la mantissa normalitzada té 24 bits significatius (23 bits de fracció emmagatzemats més el bit ocult), de manera que la precisió és de 24 bits.

Essencial 5.3: Compromís rang–precisió

Donat un nombre fix de bits, augmentar la precisió (més bits de fracció) redueix el rang (menys bits d’exponent) i viceversa. El format de precisió simple (8 bits d’exponent i 23 de fracció) és un dels compromisos que fixa l’estàndard IEEE 754; el de precisió doble (11 i 52 bits, RISC-V 5.1) n’és un altre.

Dues rectes, la dels negatius a dalt i la dels positius a sota, amb un punt per a cada valor destacat i, a sota de cadascun, el signe, l'exponent, la mantissa i la codificació hexadecimal: als extrems, NaN (0xFFFFFFFF i 0x7FFFFFFF) i ±∞ (0xFF800000 i 0x7F800000); després, el normal més gran, ±3,4·10 elevat a 38, i ±2 elevat a 127; i les potències de 2 de 2 elevat a 0 a 2 elevat a 3, separades cadascuna per 2 elevat a 23 valors. Cap al zero, una fletxa grisa indica els subnormals.

Dues rectes, la dels negatius a dalt i la dels positius a sota, amb un punt per a cada valor destacat i, a sota de cadascun, el signe, l'exponent, la mantissa i la codificació hexadecimal: als extrems, NaN (0xFFFFFFFF i 0x7FFFFFFF) i ±∞ (0xFF800000 i 0x7F800000); després, el normal més gran, ±3,4·10 elevat a 38, i ±2 elevat a 127; i les potències de 2 de 2 elevat a 0 a 2 elevat a 3, separades cadascuna per 2 elevat a 23 valors. Cap al zero, una fletxa grisa indica els subnormals.

Figura 5.3: Recta de la coma flotant IEEE 754 (precisió simple): \(\pm\infty\) i NaN als extrems (\(E=255\)) i les potències de 2 equidistants: l’eix segueix l’ordre de les codificacions, i entre dues potències de 2 consecutives n’hi ha sempre \(2^{23}\).

5.1.5 Error de representació i subdesbordament

El resultat exacte \(v\) d’una operació pot no ser representable amb un nombre finit de bits de fracció. En aquest cas cal aproximar-lo per un dels dos valors representables més pròxims \(v_0 < v < v_1\). L’error absolut de representació es defineix com \(\varepsilon = |v - \tilde{v}|\), on \(\tilde{v} \in \{v_0, v_1\}\) és el valor que tria l’arrodoniment, i l’error relatiu, com \(\eta = \varepsilon\,/\,|v|\).

Per a un nombre normalitzat qualsevol de la forma \(v_0 = m \times 2^e\), el valor representable següent és \(v_1 = (m + 2^{-23}) \times 2^e\), de manera que l’error absolut màxim és:

\[\varepsilon_{max} < 2^{e-23}\]

Dividint per \(v_0\) i tenint en compte que \(m \geq 1{,}0\), l’error relatiu queda acotat uniformement (Aprofundiment 5.1):

\[\eta_{max} < 2^{-23} = 1\ \text{ULP}\]

La quantitat \(1\ \text{ULP}\), una unit in the last place (ULP), és el valor \(0{,}0\ldots001_2\) amb un sol bit a 1 a la posició de menys pes de la mantissa. La fita \(\eta_{max} < 2^{-23}\) és independent del valor de \(v\) i depèn exclusivament de la precisió de la mantissa.

Essencial 5.4: Subdesbordament

Hi ha una excepció important a la fita d’error relatiu. Quan el valor absolut del resultat és inferior al mínim positiu normalitzat representable \(2^{E_{min}} = 2^{-126}\), si s’aproxima simplement per zero l’error relatiu és \(\eta = 1\), és a dir, de l’ordre del 100 %. Això pot invalidar completament el significat del resultat.

Quan el maquinari detecta aquesta situació, genera una excepció de subdesbordament (underflow), que permet al programari tractar-la adequadament. Els nombres subnormals (Secció 5.1.6) són el mecanisme que defineix l’IEEE 754 per reduir aquest error de forma gradual.

Donat \(v_0 = m \times 2^e\) i \(v_1 = (m + 2^{-23}) \times 2^e\), l’error absolut màxim a l’interval \((v_0, v_1)\) és:

\[\varepsilon_{max} < |v_1 - v_0| = 2^{-23} \times 2^e = 2^{e-23}\]

Per a l’error relatiu, el cas pitjor dins l’interval és el valor de menor magnitud, que és \(v_0\):

\[\eta_{max} < \frac{\varepsilon_{max}}{|v_0|} = \frac{2^{e-23}}{m \times 2^e} = \frac{2^{-23}}{m}\]

Com que la mantissa normalitzada satisfà \(m \geq 1{,}0\), resulta:

\[\eta_{max} < 2^{-23} = 1\ \text{ULP} \approx 1{,}19 \times 10^{-7}\]

Aquesta fita és independent de \(e\) i de \(m\): l’error relatiu de representació és uniformement acotat per \(1\ \text{ULP}\) arreu del rang normalitzat.

Amb arrodoniment al més pròxim (Secció 5.1.7), el valor triat és a menys de mig interval: \(\varepsilon \leq 2^{e-24}\) i \(\eta \leq 2^{-24} = \tfrac{1}{2}\ \text{ULP}\).

5.1.6 Codificacions especials

L’estàndard IEEE 754 reserva dos valors del camp d’exponent per a codificacions especials: \(E = 0000\,0000_2\) (tot zeros) i \(E = 1111\,1111_2\) (tot uns). Per tant, el rang efectiu de l’exponent queda reduït a \([-126,\ +127]\) en precisió simple. La Figura 5.4 en mostra el mapa complet.

EC 5.2: IEEE 754 — Taula de codificacions
Exponent \(E\) Fracció \(F\) Interpretació
\(0000\,0000\) \(000\ldots0\) Zero (\(\pm 0\))
\(0000\,0000\) \(\neq 000\ldots0\) Subnormal \((\pm 0{,}F \times 2^{-126})\)
\(0000\,0001\) a \(1111\,1110\) qualsevol Normalitzat \((\pm 1{,}F \times 2^{e})\)
\(1111\,1111\) \(000\ldots0\) Infinit (\(\pm\infty\))
\(1111\,1111\) \(\neq 000\ldots0\) NaN

Una taula de dues entrades. Les columnes són l'exponent E: tot zeros, altres valors i tot uns; les files, la fracció F: tot zeros i altres valors. Amb E tot zeros, F zero codifica el zero, i F no zero, un subnormal; amb E tot uns, F zero codifica l'infinit, i F no zero, NaN; amb qualsevol altre E, el valor és normalitzat, sigui quina sigui F.

Una taula de dues entrades. Les columnes són l'exponent E: tot zeros, altres valors i tot uns; les files, la fracció F: tot zeros i altres valors. Amb E tot zeros, F zero codifica el zero, i F no zero, un subnormal; amb E tot uns, F zero codifica l'infinit, i F no zero, NaN; amb qualsevol altre E, el valor és normalitzat, sigui quina sigui F.

Figura 5.4: Mapa de codificacions IEEE 754 de precisió simple: els eixos \(E\) i \(F\) determinen unívocament la interpretació de cada patró de bits.

5.1.6.1 Zero

Els formats normalitzats no poden representar el zero, ja que el bit ocult imposa \(m \geq 1{,}0\). El zero es codifica convencionalment amb tots els bits d’exponent i fracció a zero. A causa del bit de signe, existeixen dues representacions: \(+0\) i \(-0\). L’estàndard defineix que \(+0 = -0\) en les comparacions.

5.1.6.2 Subnormals

Com s’ha vist a la Secció 5.1.5, quan \(|v| < 2^{-126}\) l’error relatiu pot arribar al 100 % si s’arrodoneix a zero. Els nombres subnormals (subnormals), també anomenats denormals (denormalized), cobreixen l’interval \((0,\ 2^{-126})\) amb nombres de la forma:

\[v = \pm 0{,}F \times 2^{-126} \qquad (F \neq 0)\]

A diferència dels nombres normalitzats, no hi ha bit ocult per als subnormals: la part entera de la mantissa és \(0\) (en comptes d’\(1\)). L’exponent implícit dels subnormals és sempre \(e = -126\); és a dir, no s’obté aplicant la fórmula \(e = E_u - 127\) (que amb \(E_u = 0\) donaria \(-127\)), sinó que és, per definició, el mateix exponent que el del normal més petit (\(2^{-126}\)).

Amb subnormals, la fita d’error absolut dins l’interval \((0,\ 2^{-126})\) és la mateixa que a l’interval normalitzat adjacent \((2^{-126},\ 2^{-125})\):

\[\varepsilon_{max} < 2^{-149}\]

L’error relatiu creix de forma gradual a mesura que \(v\) s’acosta a zero, en lloc d’augmentar de cop com passaria sense subnormals. Per aquesta raó el mecanisme es coneix també com a subdesbordament gradual (gradual underflow).

En precisió simple, la fita d’error relatiu és \(2^{-23}\) a l’extrem superior de l’interval \((0,\ 2^{-126})\) i creix progressivament fins a \(2^0 = 1\) a mesura que \(v\) s’acosta a zero i la mantissa no normalitzada perd dígits significatius.

Essencial 5.5

Per a cada exponent normalitzat \(E \in [1, 254]\), la fracció \(F\) pren els \(2^{23}\) valors possibles \(\{0, 1, \ldots, 2^{23}-1\}\). Per exemple, \(F = 0\) correspon a la potència de 2 exacta \((\pm 1{,}0 \times 2^e)\), un nombre normalitzat vàlid.

Tanmateix, per als subnormals (\(E = 0\)), el rang de \(F\) és el mateix, però el cas \(F = 0\) codifica \(\pm 0\), que no és un subnormal. Per tant, hi ha \(2^{23} - 1\) subnormals per signe, enfront dels \(2^{23}\) valors per exponent dels normals. Aquesta asimetria és visible comparant Figura 5.5 amb Figura 5.3.

Dues rectes al voltant del zero, la dels negatius a dalt i la dels positius a sota, amb el signe, l'exponent, la mantissa i la codificació hexadecimal de cada punt destacat: ±0 (0x80000000 i 0x00000000); el subnormal més petit, ±1,4·10 elevat a −45 (0x80000001 i 0x00000001); el subnormal més gran, ±1,1754942·10 elevat a −38 (0x807FFFFF i 0x007FFFFF); i el normal més petit, ±2 elevat a −126 (0x80800000 i 0x00800000). Entre el zero i el subnormal més petit, un interval marcat com a rang no representable; entre aquest i els normals, els 2 elevat a 23 menys 1 subnormals.

Dues rectes al voltant del zero, la dels negatius a dalt i la dels positius a sota, amb el signe, l'exponent, la mantissa i la codificació hexadecimal de cada punt destacat: ±0 (0x80000000 i 0x00000000); el subnormal més petit, ±1,4·10 elevat a −45 (0x80000001 i 0x00000001); el subnormal més gran, ±1,1754942·10 elevat a −38 (0x807FFFFF i 0x007FFFFF); i el normal més petit, ±2 elevat a −126 (0x80800000 i 0x00800000). Entre el zero i el subnormal més petit, un interval marcat com a rang no representable; entre aquest i els normals, els 2 elevat a 23 menys 1 subnormals.

Figura 5.5: Zoom al voltant del zero: \(\pm 0\) i els subnormals (\(E=0\)) omplen el buit entre el zero i el normal més petit (\(2^{-126}\)).

5.1.6.3 Infinit

L’infinit s’incorpora a l’aritmètica per raons pràctiques. Sigui l’expressió \(1\,/\,(1 + 100/x)\) per a valors de \(x\) pròxims a zero: sense infinit, el càlcul \(100/x\) produiria una excepció de desbordament que avortaria el programa, tot i que el resultat final de l’expressió convergeix a zero. Amb infinit, la seqüència d’operacions és \(100/x = \pm\infty\), \(1 + \infty = \infty\), \(1/\infty = 0\), i el programa continua sense avortar: l’excepció de desbordament només activa el seu indicador (RISC-V 5.3).

L’infinit es codifica amb tots els bits d’exponent a 1 i tots els de fracció a zero. S’apliquen regles aritmètiques senzilles: \(1/0 = \infty\), \(1/\infty = 0\), \(x + \infty = \infty\), etc.

5.1.6.4 NaN

Algunes operacions no produeixen cap valor real vàlid: \(\sqrt{-1}\), \(\log(-1)\), \(0/0\), \(\infty - \infty\), \(0 \times \infty\). En lloc d’avortar l’execució, el resultat pren la codificació especial NaN, i l’execució continua. Qualsevol operació aritmètica posterior amb un operand NaN produeix un altre NaN (les comparacions, en canvi, retornen 0: RISC-V 5.7), de manera que l’error es propaga fins al resultat final, on el programa pot fer una única comprovació.

Un NaN es codifica amb tots els bits d’exponent a 1 i almenys un bit de fracció no nul (per distingir-lo de l’infinit). El bit de més pes de la fracció distingeix el NaN silenciós (1), que es propaga sense activar cap indicador, del senyalitzador (0), que activa NV. A RISC-V, les operacions que generen un NaN donen el NaN canònic 0x7FC00000.

5.1.7 Arrodoniment

Quan el resultat exacte \(v\) d’una operació no és representable, cal triar entre els dos valors representables \(v_0 < v < v_1\) més pròxims. Aquesta aproximació s’anomena arrodoniment. L’IEEE 754 (revisió de 2008) defineix cinc modes; els quatre d’ús més habitual són:

Mode Criteri
Al més pròxim (per defecte) El valor representable més proper a \(v\); en cas d’empat, el parell
A zero El valor de menor magnitud (truncament)
A \(+\infty\) El valor representable major
A \(-\infty\) El valor representable menor

El mode per defecte és l’arrodoniment al més pròxim (round to nearest, ties to even, RNE). Proporciona l’error mínim possible i, en el cas equidistant, tria el valor parell (el que té el bit de menys pes a 0) per evitar biaixos sistemàtics en cadenes llargues de càlculs.

Els modes d’arrodoniment a \(+\infty\) i a \(-\infty\) s’usen conjuntament en aritmètica d’intervals, una tècnica que acota els errors d’arrodoniment per garantir la fiabilitat dels resultats numèrics. L’estàndard inclou encara un cinquè mode poc emprat: «al més pròxim amb empat cap a la magnitud major» (round to nearest, ties to max magnitude, RMM). RISC-V implementa els cinc modes al registre fcsr (RISC-V 5.3).

5.1.7.1 Bits de guarda

Quan el resultat exacte d’una operació té més bits que la mantissa, els bits de guarda (els bits sobrants) determinen com s’arrodoneix. N’hi ha prou amb tres (Figura 5.6):

  • \(G\) (Guard): el primer bit descartat.
  • \(R\) (Round): el segon bit descartat.
  • \(S\) (Sticky): l’OR lògic de la resta de bits descartats.

Mantissa retinguda seguida dels tres bits descartats G, R i S que determinen l'arrodoniment.

Mantissa retinguda seguida dels tres bits descartats G, R i S que determinen l'arrodoniment.

Figura 5.6: Esquema dels bits de guarda (\(G\)), arrodoniment (\(R\)) i sticky (\(S\)) respecte de la mantissa retinguda, amb la regla RNE.

La regla RNE en funció de \(G\), \(R\) i \(S\) és:

\(G\) \(R\) \(S\) Acció
0 — — A l’anterior (truncar)
1 0 0 Empat → al parell
1 1 — Al següent (sumar 1 ULP)
1 0 1 Al següent (sumar 1 ULP)
Exemple 5.4: Arrodoniment RNE

Arrodoneix les mantisses de 6 bits següents a 3 bits (els 3 bits descartats són directament \(G\), \(R\) i \(S\)):

Valor exacte \(G\,R\,S\) Criteri Resultat
\(101{,}010\) \(0\;1\;0\) \(G=0\) → a l’anterior \(101\)
\(101{,}011\) \(0\;1\;1\) \(G=0\) → a l’anterior \(101\)
\(101{,}101\) \(1\;0\;1\) \(G=1\), \(R\) o \(S=1\) → al següent \(110\)
\(101{,}100\) \(1\;0\;0\) \(G=1\), \(R=S=0\) → empat → al parell (\(110\)) \(110\)
\(100{,}100\) \(1\;0\;0\) \(G=1\), \(R=S=0\) → empat → al parell (\(100\)) \(100\)

Durant una suma o resta, la mantissa del sumand de menor magnitud es desplaça a la dreta per igualar els exponents, i alguns bits queden fora de la mantissa de \(p\) bits. Descartar-los introdueix error, mentre que conservar-los tots requeriria un sumador de mida arbitrària. Es demostra que els tres bits \(G\), \(R\) i \(S\) (Secció 5.1.7.1) basten per obtenir exactament el mateix resultat arrodonit que amb tots els bits: \(G\) pot incorporar-se a la mantissa en normalitzar, \(R\) decideix l’arrodoniment i \(S\) (l’OR de la resta) distingeix el cas equidistant.

Exemple (precisió \(p = 6\) bits):

Resta amb G, R i S (p = 6)
  1,00000
- 0,00001 101   ← G=1, R=0, S=1 → al següent
-----------
  0,11110 011
Normalitzar:    1,11100 11
Arrodonir:      1,11101     (G=1, R=1 → al següent)

Amb tots els bits, el resultat arrodonit és idèntic: \(1{,}11101\).

5.2 Operacions en coma flotant

5.2.1 Suma i resta

L’algorisme de suma i resta en coma flotant segueix cinc passos:

  1. Igualar els exponents al major dels dos operands, desplaçant a la dreta la mantissa del de menor magnitud.
  2. Sumar o restar les mantisses segons els signes dels operands.
  3. Normalitzar el resultat, desplaçant la mantissa fins que la part entera sigui 1, ajustant l’exponent en conseqüència; cal comprovar desbordament/subdesbordament.
  4. Arrodonir la mantissa a \(p\) bits.
  5. Codificar signe, exponent i fracció.
Exemple 5.5: Suma en coma flotant, base 10

Calcula \(z = x + y\) amb \(x = 9{,}999 \times 10^1\) i \(y = 1{,}680 \times 10^{-1}\), amb 4 dígits de precisió a la mantissa.

Pas 1. Igualar exponents (al major, \(e = 1\)):

\[y = 1{,}680 \times 10^{-1} = 0{,}01680 \times 10^1\]

Pas 2. Sumar les mantisses:

\[\begin{array}{r} 9{,}999 \\ +\ 0{,}01680 \\ \hline 10{,}01580 \end{array} \times 10^1\]

Pas 3. Normalitzar:

\[z = 1{,}001580 \times 10^2 \qquad (e = 2 \in [-99,+99],\ \text{cap excepció})\]

Pas 4. Arrodonir a 4 dígits (dígits descartats en negreta: \(\mathbf{580}\); com que superen la meitat, s’arrodoneix al següent):

\[z \approx 1{,}002 \times 10^2\]

Exemple 5.6: Suma en coma flotant, precisió simple

Calcula \(z = x + y\) amb \(x=\) 0x3F40000D i \(y=\) 0xC0800004.

Descomposició dels operands:

\[x = 0\;|\;0111\,1110\;|\;100\,0000\,0000\,0000\,0000\,1101_2\] \[y = 1\;|\;1000\,0001\;|\;000\,0000\,0000\,0000\,0000\,0100_2\]

Descodificació dels operands:

  • \(x\): \(S=0\), \(E_u = 126 \Rightarrow e = -1\); \(x = +1{,}1000\,0000\,0000\,0000\,0001\,101_2 \times 2^{-1}\).
  • \(y\): \(S=1\), \(E_u = 129 \Rightarrow e = +2\); \(y = -1{,}0000\,0000\,0000\,0000\,0000\,100_2 \times 2^{2}\).

Pas 1. Igualar exponents (al major, \(e = 2\); cal desplaçar \(x\) 3 posicions a la dreta):

\[x = +0{,}0011\,0000\,0000\,0000\,0000\,0011\,01_2 \times 2^2\]

Pas 2. Restar les magnituds (signes oposats; \(|y| > |x|\); el resultat pren el signe de \(y\)):

\[\begin{array}{r} 1{,}0000\,0000\,0000\,0000\,0000\,100\phantom{00} \\ -\ 0{,}0011\,0000\,0000\,0000\,0000\,001101 \\ \hline 0{,}1101\,0000\,0000\,0000\,0000\,010011 \end{array} \times 2^2\]

Pas 3. Normalitzar (desplaçar 1 posició a l’esquerra, \(e = 2 - 1 = 1\)):

\[|z| = 1{,}1010\,0000\,0000\,0000\,0000\,10011_2 \times 2^1\]

\(e = 1 \in [-126,+127]\): cap excepció.

Pas 4. Arrodonir a 24 bits significatius (bits de guarda en negreta: \(\mathbf{11}\); \(G=1\), \(R=1\) → al següent):

\[\begin{array}{r} 1{,}1010\,0000\,0000\,0000\,0000\,100 \\ +\hspace{11.3em}1 \\ \hline 1{,}1010\,0000\,0000\,0000\,0000\,101 \end{array} \times 2^1\]

Pas 5. Codificar (\(S = 1\), \(E_u = 1 + 127 = 128 = 1000\,0000_2\)):

\[z = 1\;|\;1000\,0000\;|\;101\,0000\,0000\,0000\,0000\,0101_2 = \texttt{0xC0500005}\]

Error de representació:

\[\varepsilon = |1{,}1010\,0000\,0000\,0000\,0000\,101 - 1{,}1010\,0000\,0000\,0000\,0000\,10011| \times 2^1\] \[= 0{,}0000\,0000\,0000\,0000\,0000\,0000\,1 \times 2^1 = 1{,}0_2 \times 2^{-24} \approx 6{,}0 \times 10^{-8}\]

5.2.2 Multiplicació i divisió

L’algorisme de multiplicació i divisió és més simple que el de suma i resta perquè no cal igualar exponents:

  1. Sumar els exponents (multiplicació) o restar-los (divisió).
  2. Multiplicar o dividir les mantisses.
  3. Determinar el signe: negatiu si els operands tenen signes diferents.
  4. Normalitzar el resultat: cal comprovar desbordament/subdesbordament.
  5. Arrodonir la mantissa a \(p\) bits.
  6. Codificar signe, exponent i fracció.
Exemple 5.7: Multiplicació en coma flotant, base 10

Calcula \(z = x \times y\) amb \(x = -2{,}111 \times 10^{10}\) i \(y = 9{,}200 \times 10^{-5}\), amb 4 dígits de precisió.

Pas 1. Sumar els exponents: \(10 + (-5) = 5\)

Pas 2. Multiplicar les mantisses:

\[2{,}111 \times 9{,}200 = 19{,}4212\]

Pas 3. Signe: negatiu (signes oposats).

Pas 4. Normalitzar:

\[|z| = 1{,}94212 \times 10^6 \qquad (e = 6 \in [-99,+99])\]

Pas 5. Arrodonir a 4 dígits (dígits descartats: \(\mathbf{12}\); primer dígit descartat \(< 5\) → a l’anterior):

\[z \approx -1{,}942 \times 10^6\]

Exemple 5.8: Multiplicació en coma flotant, precisió simple

Calcula \(z = x \times y\) amb \(x=\) 0x3F600000 i \(y=\) 0xBED00002.

Descomposició dels operands:

\[x = 0\;|\;0111\,1110\;|\;110\,0000\,0000\,0000\,0000\,0000_2\] \[y = 1\;|\;0111\,1101\;|\;101\,0000\,0000\,0000\,0000\,0010_2\]

Descodificació:

  • \(x\): \(S=0\), \(e=-1\), mantissa \(1{,}1100\,0000\,0000\,0000\,0000\,000_2\).
  • \(y\): \(S=1\), \(e=-2\), mantissa \(1{,}1010\,0000\,0000\,0000\,0000\,010_2\).

Pas 1. Sumar els exponents: \(e = -1 + (-2) = -3\)

Pas 2. Multiplicar les mantisses (es treballa amb els valors absoluts):

\[\begin{array}{r} 1{,}11 \\ \times\ 1{,}1010\,0000\,0000\,0000\,0000\,010 \\ \hline 10{,}1101\,1000\,0000\,0000\,0000\,0111 \end{array}\]

Pas 3. Signe: negatiu (signes oposats).

Pas 4. Normalitzar (desplaçar 1 posició a la dreta, \(e = -3 + 1 = -2\)):

\[|z| = 1{,}0110\,1100\,0000\,0000\,0000\,0011\,1_2 \times 2^{-2}\]

\(e = -2 \in [-126,+127]\): cap excepció.

Pas 5. Arrodonir a 24 bits (bits de guarda: \(\mathbf{11}\); \(G=1\), \(R=1\) → al següent):

\[\begin{array}{r} 1{,}0110\,1100\,0000\,0000\,0000\,001 \\ +\hspace{10.3em}1 \\ \hline 1{,}0110\,1100\,0000\,0000\,0000\,010 \end{array} \times 2^{-2}\]

Pas 6. Codificar (\(S=1\), \(E_u = -2 + 127 = 125 = 0111\,1101_2\)):

\[z = 1\;|\;0111\,1101\;|\;011\,0110\,0000\,0000\,0000\,0010_2 = \texttt{0xBEB60002}\]

Exemple 5.9: Divisió en coma flotant, precisió simple

Calcula \(z = x / y\) amb \(x=\) 0x41000000 i \(y=\) 0x40400000.

Descodificació:

  • \(x\): \(S=0\), \(e=3\), mantissa \(= +1{,}0_2\) → \(x = +8{,}0\)
  • \(y\): \(S=0\), \(e=1\), mantissa \(= +1{,}1_2\) → \(y = +3{,}0\)

Pas 1. Restar els exponents: \(e = 3 - 1 = 2\)

Pas 2. Dividir les mantisses:

\[\frac{1{,}0_2}{1{,}1_2} = 0{,}1010\,1010\,1010\ldots_2\]

A diferència de la multiplicació, que dona un producte finit de \(2p\) bits com a màxim, la divisió de mantisses sol donar un quocient periòdic que no acaba, de manera que l’arrodoniment és gairebé sempre necessari.

Pas 3. Signe: positiu (mateix signe).

Pas 4. Normalitzar (desplaçar 1 posició a l’esquerra, \(e = 2 - 1 = 1\)):

\[|z| = 1{,}0101\,0101\,0101\,0101\,0101\,010\;\mathbf{1010}\ldots_2 \times 2^{1}\]

\(e = 1 \in [-126,+127]\): cap excepció.

Pas 5. Arrodonir a 24 bits (bits de guarda: \(G=1\), \(R=0\), \(S=1\) → al següent):

\[\begin{array}{r} 1{,}0101\,0101\,0101\,0101\,0101\,010 \\ +\hspace{10.3em}1 \\ \hline 1{,}0101\,0101\,0101\,0101\,0101\,011 \end{array} \times 2^{1}\]

Pas 6. Codificar (\(S=0\), \(E_u = 1 + 127 = 128 = 1000\,0000_2\)):

\[z = 0\;|\;1000\,0000\;|\;010\,1010\,1010\,1010\,1010\,1011_2 = \texttt{0x402AAAAB} \approx 2{,}6667\]

5.2.3 No-associativitat

La suma d’enters en Ca2 és associativa: \((a + b) + c = a + (b + c)\) sempre. En coma flotant, aquesta propietat simplement no es compleix a causa dels arrodoniments intermedis.

Exemple 5.10: Contraexemple de l’associativitat

Siguin \(x = -1{,}1_2 \times 2^{127}\), \(y = +1{,}1_2 \times 2^{127}\), \(z = 1{,}0_2 \times 2^0\). Comprova que \(x + (y + z) \neq (x + y) + z\).

\(x + (y + z)\):

\(y + z\): cal igualar exponents; \(z = 2^0\) és tan petit respecte de \(y \approx 2^{127}\) que queda fora de la precisió i no contribueix a la suma: \[y + z = 1{,}1_2 \times 2^{127} + 0{,}0\ldots0\ 1_2 \times 2^{127} \approx 1{,}1_2 \times 2^{127}\] \[x + (y + z) = -1{,}1_2 \times 2^{127} + 1{,}1_2 \times 2^{127} = 0{,}0\]

\((x + y) + z\):

\[x + y = -1{,}1_2 \times 2^{127} + 1{,}1_2 \times 2^{127} = 0{,}0\] \[(x + y) + z = 0{,}0 + 1{,}0 = 1{,}0\]

Per tant, \(x + (y + z) = 0{,}0 \neq 1{,}0 = (x + y) + z\).

Essencial 5.6: La suma en coma flotant no és associativa

La no-associativitat de la suma en coma flotant té conseqüències pràctiques importants:

  • Paral·lelització: dividir una suma de \(n\) elements en subcadenes i sumar els resultats parcials pot donar un resultat diferent del càlcul seqüencial. El resultat depèn del nombre de processadors i de l’ordre d’execució.
  • Comparacions d’igualtat: comparar el resultat d’una cadena de càlculs amb un valor exacte (== 1.0) és perillós. És preferible comprovar si el resultat es troba dins d’un interval de tolerància \(1{,}0 \pm \varepsilon\).

5.3 Coma flotant a RISC-V (RV32F)

5.3.1 Registres i ABI

L’extensió F de RISC-V afegeix 32 registres de coma flotant de 32 bits, f0–f31, independents dels registres enters d’RV32I. L’ABI ilp32f assigna noms i rols a cadascun, seguint el mateix esquema de temporals, segurs i paràmetres que els registres enters (RISC-V 2.24).

RISC-V 5.2: RV32F ABI — Registres de coma flotant
ISA ABI Propòsit Saver
f0–f7 ft0–ft7 Temporals Caller
f8–f9 fs0–fs1 Segurs Callee
f10–f11 fa0–fa1 Arguments de subrutines / Valors de retorn Caller
f12–f17 fa2–fa7 Arguments de subrutines Caller
f18–f27 fs2–fs11 Segurs Callee
f28–f31 ft8–ft11 Temporals Caller

A més dels registres f0–f31, l’extensió F afegeix el registre de control i estat de coma flotant fcsr (Floating-Point Control and Status Register), que controla el mode d’arrodoniment i registra les excepcions produïdes per les operacions de coma flotant (cosa que fa obligatòria l’extensió Zicsr, que es presenta a T9: RISC-V 9.2).

RISC-V 5.3: RV32F ISA — Registre de control fcsr

Reservat(31–8), frm(7–5), NV(4), DZ(3), OF(2), UF(1), NX(0).

Reservat(31–8), frm(7–5), NV(4), DZ(3), OF(2), UF(1), NX(0).

Bits Camp Descripció
31:8 — Reservats (llegeixen com a 0)
7:5 frm Mode d’arrodoniment
4:0 fflags Indicadors d’excepció (sticky)

Modes d’arrodoniment (frm):

Codi Mnemònic Criteri
000 RNE Al més pròxim, empat al parell (per defecte, Secció 5.1.7)
001 RTZ A zero (truncament)
010 RDN A \(-\infty\)
011 RUP A \(+\infty\)
100 RMM Al més pròxim, empat al de major magnitud

Indicadors d’excepció (fflags):

Bit Mnemònic Excepció
4 NV Operació invàlida (\(\sqrt{-1}\), \(0/0\), \(\infty - \infty\)…)
3 DZ Divisió per zero
2 OF Desbordament
1 UF Subdesbordament
0 NX Resultat inexacte (arrodoniment)

Els indicadors són sticky: un cop activats pel maquinari, romanen actius fins que el programari els esborra explícitament. A RISC-V, aquestes excepcions no interrompen l’execució: només activen l’indicador corresponent, i és el programari qui l’ha de consultar.

EC 5.3: Paral·lelisme entre registres enters i de coma flotant

Els noms ABI dels registres de coma flotant segueixen el mateix esquema que els dels registres enters:

Categoria Enters Coma flotant
Temporals t0–t6 ft0–ft11
Segurs s0–s11 fs0–fs11
Arguments / retorn a0–a7 fa0–fa7
Essencial 5.7: Preservació dels registres de coma flotant

Les convencions de preservació s’apliquen als registres de coma flotant exactament igual que als enters:

  • Els registres temporals (ft0–ft11) i els d’arguments (fa0–fa7) no es preserven: poden ser modificats lliurement per qualsevol subrutina. Si la funció que crida en necessita el valor després d’una crida, els ha de desar ella mateixa.
  • Els registres segurs (fs0–fs11) han de ser restaurats al seu valor original per la subrutina que els modifica. Si una subrutina usa fs0, ha de desar-lo al pròleg i restaurar-lo a l’epíleg, exactament igual que faria amb s0.

El pròleg i l’epíleg d’una subrutina que usa registres segurs de coma flotant segueixen el mateix patró que per als enters (Secció 3.9.4):

RV32IF
sub:
        addi    sp, sp, -8
        fsw     fs0, 0(sp)      # desa fs0 (callee-saved)
        fsw     fs1, 4(sp)      # desa fs1 (callee-saved)

        # cos de la subrutina

        flw     fs0, 0(sp)      # restaura fs0
        flw     fs1, 4(sp)      # restaura fs1
        addi    sp, sp, 8
        ret

5.3.2 Instruccions

Les instruccions aritmètiques, de comparació i de conversió de precisió simple porten el sufix .s (fadd.s, flt.s, fcvt.w.s); les de lectura i escriptura (flw, fsw) i les de moviment de bits (fmv.x.w, fmv.w.x) porten la lletra de la mida, com les d’enters. Els operands de les instruccions aritmètiques són tots registres de coma flotant: no hi ha operands immediats flotants ni instruccions aritmètiques que combinin registres enters i flotants. Els registres enters només hi apareixen com a base d’adreça (flw, fsw), com a destí de les comparacions i com a origen o destí de les conversions i dels moviments.

RISC-V 5.4: RV32F ISA — Lectura i escriptura
Mnemònic Operands Operació Nom Tipus
flw rd, offset(rs1) \(rd \leftarrow M_w[rs1 + SignExt(offset)]\) Float Load Word I
fsw rs2, offset(rs1) \(M_w[rs1 + SignExt(offset)] \leftarrow rs2\) Float Store Word S

rs1 és sempre un registre enter. L’adreça efectiva ha de ser múltiple de 4 (alineació de paraula).

RISC-V 5.5: RV32F ISA — Instruccions aritmètiques
Mnemònic Operands Operació Nom Tipus
fadd.s rd, rs1, rs2 \(rd \leftarrow rs1 + rs2\) Float Add R
fsub.s rd, rs1, rs2 \(rd \leftarrow rs1 - rs2\) Float Subtract R
fmul.s rd, rs1, rs2 \(rd \leftarrow rs1 \times rs2\) Float Multiply R
fdiv.s rd, rs1, rs2 \(rd \leftarrow rs1 / rs2\) Float Divide R
fsqrt.s rd, rs1 \(rd \leftarrow \sqrt{rs1}\) Float Square Root R
fmin.s rd, rs1, rs2 \(rd \leftarrow \min(rs1, rs2)\) Float Minimum R
fmax.s rd, rs1, rs2 \(rd \leftarrow \max(rs1, rs2)\) Float Maximum R

L’extensió F inclou quatre instruccions de multiplicació i suma fusionades (fused multiply-add, FMA), que calculen un producte i una suma en una sola instrucció (RISC-V 2.6). La bàsica és fmadd.s:

RV32IF
        fmadd.s ft0, ft1, ft2, ft3    # ft0 <- ft1 * ft2 + ft3

Les altres tres només canvien els signes: fmsub.s calcula \(rs1 \times rs2 - rs3\); fnmadd.s, \(-(rs1 \times rs2) - rs3\), i fnmsub.s, \(-(rs1 \times rs2) + rs3\).

Com que necessiten tres registres font, no caben al format R i en tenen un de propi, el R4 (Figura 5.7): el camp rs3 (fs3 a la figura, perquè és un registre de coma flotant) ocupa els bits \([31{:}27]\), on el format R té la part alta de funct7; funct2 (bits \([26{:}25]\)) indica la precisió (00 per a la precisió simple), i funct3 conté el mode d’arrodoniment.

fs3(31–27), funct2(26–25), fs2(24–20), fs1(19–15), funct3(14–12), fd(11–7), opcode(6–0).

fs3(31–27), funct2(26–25), fs2(24–20), fs1(19–15), funct3(14–12), fd(11–7), opcode(6–0).

Figura 5.7: Format d’instrucció R4 (RV32F), el de les instruccions fusionades.

La diferència amb la seqüència fmul.s + fadd.s no és només de velocitat. fmadd.s calcula \(rs1 \times rs2 + rs3\) com si tingués precisió infinita i arrodoneix una sola vegada, al final (Secció 5.1.7); la seqüència de dues instruccions arrodoneix dues vegades, el producte i la suma. Per això el resultat de fmadd.s és, en general, més precís, i pot no coincidir amb el de la seqüència: com a la Secció 5.2.3, els arrodoniments intermedis canvien el resultat.

RISC-V 5.6: Pseudoinstruccions — Còpia entre registres de coma flotant: fmv.s
Pseudoinstrucció Operació Expansió
fmv.s rd, rs \(rd \leftarrow rs\) fsgnj.s rd, rs, rs

RARS admet fmv.s directament.

RISC-V 5.7: RV32F ISA — Comparacions
Mnemònic Operands Operació Nom Tipus
feq.s rd, rs1, rs2 \(rd \leftarrow (rs1 = rs2)\ ?\ 1 : 0\) Float Equal R
flt.s rd, rs1, rs2 \(rd \leftarrow (rs1 < rs2)\ ?\ 1 : 0\) Float Less Than R
fle.s rd, rs1, rs2 \(rd \leftarrow (rs1 \leq rs2)\ ?\ 1 : 0\) Float Less or Equal R

El resultat s’escriu en un registre enter rd (0 o 1). Per implementar un salt condicional, cal combinar la comparació amb beq o bne:

RV32IF
        flt.s   t0, fa0, fa1    # t0 = 1 si fa0 < fa1, si no 0
        bne     t0, zero, label # salta si fa0 < fa1

Si algun operand és NaN, totes les comparacions retornen 0.

RISC-V 5.8: RV32F ISA — Conversions i moviments

Conversions (amb conversió numèrica):

Mnemònic Operands Operació Nom
fcvt.s.w rd, rs1 \(rd \leftarrow (float)\ rs1\) Enter → flotant
fcvt.s.wu rd, rs1 \(rd \leftarrow (float)\ rs1\) Natural → flotant
fcvt.w.s rd, rs1 \(rd \leftarrow (int)\ rs1\) Flotant → enter (arrodoniment segons rm; rtz per a la conversió de tipus de C)
fcvt.wu.s rd, rs1 \(rd \leftarrow (unsigned)\ rs1\) Flotant → natural (arrodoniment segons rm; rtz per a la conversió de tipus de C)

rd de fcvt.s.w/fcvt.s.wu és un registre de coma flotant; rs1 és un registre enter. A fcvt.w.s/fcvt.wu.s és a l’inrevés.

Moviments (còpia de bits sense conversió numèrica):

Mnemònic Operands Operació Nom
fmv.w.x rd, rs1 \(rd \leftarrow rs1\) Enter → flotant (bits)
fmv.x.w rd, rs1 \(rd \leftarrow rs1\) Flotant → enter (bits)

fmv.w.x i fmv.x.w copien el patró de 32 bits sense cap conversió: el valor numèric en coma flotant del resultat és en general diferent del valor enter de l’operand.

Exemple 5.11: Conversió entre float i int: rtz vs. arrodoniment

Tradueix la funció C del Codi 5.1, que converteix un float a int fent una conversió de tipus explícita:

Codi 5.1: Conversió explícita d’un float a int.
C
int truncar(float x) {
    return (int) x;
}

La conversió (int) de C sempre trunca cap a zero (per als valors que caben en un int). Per traduir-ho cal indicar explícitament el mode d’arrodoniment rtz a fcvt.w.s:

RV32IF
truncar:
        fcvt.w.s a0, fa0, rtz      # a0 = (int) x  [trunca cap a zero, com el cast de C]
        ret

Si s’escriu fcvt.w.s a0, fa0 (sense operand de mode) l’assemblador hi assigna dyn (mode dinàmic): la conversió s’arrodoneix segons el mode vigent al camp frm del fcsr (RISC-V 5.3), que per defecte és RNE (al més pròxim, empat al parell). Per a \(x = 2{,}5\), tots dos modes donen el mateix resultat, però per motius diferents: rtz trunca i RNE desempata al parell:

Instrucció Mode Resultat
fcvt.w.s a0, fa0, rtz Truncament (conversió (int) de C) \(2\)
fcvt.w.s a0, fa0 dyn → RNE per defecte \(2\) (empat, al parell)

Per a \(x = 3{,}5\), en canvi, la diferència és evident: rtz dona \(3\); dyn/RNE dona \(4\) (empat, al parell). La instrucció fcvt.w.s trunca només quan se n’indica el mode rtz.

RARS 1.6 no segueix l’especificació en el primer cas: amb RNE (el mode per defecte, o rne explícit), fcvt.w.s hi dona \(3\) per a \(2{,}5\) (i \(-3\) per a \(-2{,}5\)), tot i que \(0{,}5\) i \(1{,}5\) sí que els desempata al parell (\(0\) i \(2\)).

EC 5.4: No hi ha immediats flotants

Cap instrucció d’RV32F admet un operand constant directament a la instrucció. La manera habitual de fer servir una constant de coma flotant és declarar-la al segment .data i llegir-la de memòria amb la + flw (també es pot carregar el patró de bits en un registre enter amb li i copiar-lo amb fmv.w.x):

RV32IF
.data
const_u: .float 1.0

.text
        la      t0, const_u     # t0 <- @const_u
        flw     ft0, 0(t0)      # ft0 <- 1.0

5.3.3 Exemple de traducció

Exemple 5.12: Traducció d’una funció amb float

Tradueix a RV32F la funció C del Codi 5.2:

Codi 5.2: Funció amb una condició i operacions de coma flotant.
C
float func(float x) {
    if (x < 1.0f)
        return x * x;
    else
        return 2.0f - x;
}

El paràmetre x arriba per fa0 i el resultat es retorna per fa0.

Pas 1. Constants. No hi ha immediats flotants: es declaren 1.0 i 2.0 al .data.

Pas 2. Registres segurs. func és una subrutina fulla: no fa cap crida. No cal preservar ra ni cap registre segur.

Pas 3. Codi.

RV32IF
.data
const_1: .float 1.0
const_2: .float 2.0

.text
func:
        la      t0, const_1
        flw     ft0, 0(t0)      # ft0 <- 1.0
        la      t1, const_2
        flw     ft1, 0(t1)      # ft1 <- 2.0

        flt.s   t2, fa0, ft0    # t2 = (x < 1.0) ? 1 : 0
        beq     t2, zero, sino  # si no (x >= 1.0), salta

        fmul.s  fa0, fa0, fa0   # return x * x
        ret

sino:
        fsub.s  fa0, ft1, fa0   # return 2.0 - x
        ret