Un número de coma flotante es un número que puede tener parte fraccionaria, como -1.23 o 3.1415.
Para representar un número así como una secuencia de bytes, x86-64 usa el estándar IEEE-754. Los dos formatos más comunes de este estándar son los números de coma flotante de precisión simple y de precisión doble. x86-64 admite ambos.
Un número de coma flotante de precisión simple ocupa 32 bits, mientras que uno de precisión doble ocupa 64 bits.
En x86-64 hay 16 registros dedicados a la aritmética de coma flotante, llamados xmm0 a xmm15.
Esos registros tienen al menos 128 bits de ancho, pero los procesadores recientes los amplían a 256 o 512 bits. Sin embargo, en la práctica, la mayoría de los números de coma flotante solo usan los 32 o 64 bits más bajos, según la precisión deseada.
Existe una familia de instrucciones para convertir entre formatos numéricos: cvtxx2yy.
Tanto xx como yy no son literales, sino marcadores de posición:
xx es un marcador de posición para el tipo del operando origen.yy es un marcador de posición para el tipo del operando destino.Este marcador de posición puede adoptar muchas formas, entre ellas:
| marcador de posición | significado |
|---|---|
si |
entero con signo |
ss |
coma flotante de 32 bits |
sd |
coma flotante de 64 bits |
Por ejemplo:
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
Ten en cuenta que es posible convertir un entero de 32 bits en un número de coma flotante de 64 bits o un entero de 64 bits en un número de coma flotante de 32 bits.
Del mismo modo, también es posible convertir un número de coma flotante de 32 bits en un entero de 64 bits o un número de coma flotante de 64 bits en un entero de 32 bits.
En otras palabras, el tamaño de ambos operandos en una instrucción cvtxx2yy no tiene por qué ser el mismo:
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
Cuando el número de coma flotante no se puede expresar exactamente como un entero, cvtxx2si redondea el número según el control de redondeo de un registro especial llamado mxcsr.
Este puede ser un redondeo al más cercano, hacia abajo, hacia arriba o un truncamiento.
No hay un valor predeterminado garantizado al entrar en una función.
Ten en cuenta que un resultado exacto no se redondea y, por tanto, no le afecta el control de redondeo.
Por ejemplo, 42.0 siempre se convertiría a 42.
Existe una familia similar de instrucciones con el formato cvttxx2si (con una t adicional) que siempre trunca el resultado.
La familia de instrucciones roundxx convierte un número de coma flotante del operando origen en un número de coma flotante redondeado que se almacena en el operando destino.
Aquí, xx es un marcador de posición para el tipo de los operandos, como de costumbre: ss para coma flotante de precisión simple y sd para coma flotante de precisión doble.
Esas instrucciones toman un número constante como tercer operando. Este número indica el modo de redondeo:
| Número | Modo |
|---|---|
| 0 | Al más cercano |
| 1 | Hacia abajo (suelo) |
| 2 | Hacia arriba (techo) |
| 3 | Truncamiento |
Por ejemplo:
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
Como esas instrucciones producen un resultado exacto, se pueden usar como preparación para un cvtxx2si.
Muchas operaciones con números de coma flotante usan instrucciones con un nombre similar al de su equivalente para enteros, pero añadiendo al final el marcador de posición necesario (ss o sd).
Esas instrucciones son de dos operandos, con la semántica habitual, incluso las que realizan multiplicación o división:
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)
Las instrucciones para mover de un registro XMM a otro son 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
No es posible mover un valor inmediato (un entero con signo constante) directamente a un registro xmm.
Este número primero debe moverse a un registro de uso general y luego convertirse 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
Hay dos grupos de instrucciones para comparar dos números de coma flotante.
Tienen la forma comixx y ucomixx, donde xx es ss o sd.
Ambas establecen los flags de rflags como de costumbre, siguiendo la misma semántica que cmp.
La diferencia entre ellas es que ucomixx establece el parity flag (PF) si uno de los dos números de coma flotante tiene un valor NaN, mientras que comixx lanza una excepción en ese caso.
La comparación entre valores de coma flotante es sin signo:
ucomisd xmm0, xmm1
ja .greater ; 'ja' must be used for "greater", not 'jg'
jb .lesser ; 'jb' must be used for "lesser", not 'jl'
En la ABI de System V, los ocho primeros argumentos de coma flotante se pasan a las funciones en orden, de xmm0 a xmm7.
Los valores de coma flotante se devuelven de las funciones en xmm0 y, si es necesario, en xmm1.
Todos los registros xmm están disponibles para usarlos libremente; ninguno está reservado para el llamador.
Los valores de coma flotante se pueden definir en memoria siguiendo la misma semántica que los enteros del mismo tamaño. NASM (The Netwide Assembler, el ensamblador que usa este track) también permite declarar valores de coma flotante en notación científica.
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
En este ejercicio vas a escribir código para ayudar a un freelancer a comunicarse con un jefe de proyecto. Tu tarea es proporcionar unas cuantas utilidades para calcular rápidamente tarifas diarias y mensuales, opcionalmente con un descuento dado.
Primero establecemos unas cuantas reglas entre el freelancer y el jefe de proyecto:
A veces, el freelancer ofrece aplicar un descuento sobre su tarifa diaria (por ejemplo, para sus clientes más fieles o para clientes sin ánimo de lucro).
Los descuentos se representan como números decimales que indican porcentajes; por ejemplo, 25.0 (25 %).
Estos son los sufijos de las instrucciones de coma flotante:
| Sufijo | Tamaño del operando |
|---|---|
ss |
32 bits |
sd |
64 bits |
Estas son algunas de las instrucciones mencionadas en este concepto:
| Instrucción | Descripción |
|---|---|
| movss/movsd a, b | copia b en 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 | convierte el entero b en el número de coma flotante a |
| roundss/roundsd a, b, c | redondea el número de coma flotante b según c y lo almacena en a |
| cvtss2si/cvtsd2si a, b | convierte el número de coma flotante b en el entero a |
| comiss/comisd a, b | hace una comparación entre a y b, lanza una excepción si NaN
|
| ucomiss/ucomisd a, b | hace una comparación entre a y b, establece PF si NaN
|
Estos son los números de control de redondeo en roundss y roundsd:
| Número | Modo |
|---|---|
| 0 | Al más cercano |
| 1 | Hacia abajo |
| 2 | Hacia arriba |
| 3 | Truncar |
Implementa una función llamada daily_rate que calcule la tarifa diaria a partir de una tarifa por hora recibida como parámetro, como número de coma flotante de 64 bits.
El contrato define que un día tiene 8 horas facturables.
daily_rate(60)
// => 480.0
La tarifa diaria devuelta debe ser un número de coma flotante de 64 bits.
Implementa una función apply_discount que calcule el precio después de aplicar un descuento.
Debe aceptar dos parámetros: el precio original y el porcentaje de descuento, ambos como números de coma flotante de 64 bits.
apply_discount(150.0, 10.0)
// => 135.0
El valor devuelto debe ser siempre un número de coma flotante de 64 bits.
Implementa una función monthly_rate que calcule la tarifa mensual con descuento.
Recibe dos parámetros: una tarifa por hora y el porcentaje de descuento, ambos como números de coma flotante de 64 bits.
monthly_rate(77.0, 10.5)
// => 12130
La tarifa mensual devuelta debe redondearse hacia arriba (tomar el techo) al entero de 64 bits más cercano.
Implementa una función days_in_budget que recibe como parámetros:
Esta función debe devolver el número de días completos de trabajo que cubre el presupuesto.
days_in_budget(20000, 80, 11.0)
// => 35
El número de días devuelto debe redondearse hacia abajo (tomar el suelo) al entero de 32 bits más cercano.
Regístrate en Exercism para aprender y dominar x86-64 Assembly con 22 conceptos130 ejercicios y mentoría humana real, todo gratis.