Un número de punto flotante es un número que puede tener una 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 punto flotante de precisión simple y de precisión doble. x86-64 admite ambos.
Un número de punto flotante de precisión simple ocupa 32 bits de ancho, mientras que uno de precisión doble ocupa 64 bits de ancho.
En x86-64 hay 16 registros dedicados a hacer aritmética de punto flotante, llamados xmm0 hasta xmm15.
Esos registros tienen al menos 128 bits de ancho, pero los procesadores recientes aumentan esto a 256 o 512 bits. Sin embargo, en la práctica, la mayoría de los números de punto 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 de números: 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 fuente.yy es un marcador de posición para el tipo del operando destino.Este marcador de posición puede tomar muchas formas, entre ellas:
| marcador de posición | significado |
|---|---|
si |
entero con signo |
ss |
punto flotante de 32 bits |
sd |
punto 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
Nota que es posible convertir un entero de 32 bits a un número de punto flotante de 64 bits, o un entero de 64 bits a un número de punto flotante de 32 bits.
En otras palabras, el tamaño de ambos operandos en un cvtxx2yy no necesita 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 punto 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.
Esto puede ser un redondeo al más cercano, un redondeo hacia abajo, un redondeo hacia arriba o un truncamiento.
No hay un valor predeterminado garantizado al entrar a la función.
Nota que un resultado exacto no se redondea y, por lo tanto, no lo 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 extra) que siempre trunca el resultado.
La familia de instrucciones roundxx convierte un número de punto flotante del operando fuente en un número de punto flotante redondeado que se guarda en el operando destino.
Aquí, xx es un marcador de posición para el tipo de los operandos, como de costumbre: ss para números de punto flotante de precisión simple y sd para los 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 |
| 2 | Hacia arriba |
| 3 | Truncar |
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 punto flotante usan instrucciones con un nombre similar al de su equivalente para enteros, pero agregando el marcador de posición necesario (ss o sd) al final.
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 inmediato (un entero con signo constante) directamente a un registro xmm.
Este número primero debe moverse a un registro de propósito 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 hacer una comparación entre dos números de punto flotante.
Toman la forma comixx y ucomixx, donde xx es ss o sd.
Ambas establecen banderas en rflags como de costumbre, siguiendo la misma semántica que cmp.
La diferencia entre ellas es que ucomixx establece la parity flag (PF) si uno de los dos números de punto flotante tiene un valor NaN, mientras que comixx genera una excepción en ese caso.
La comparación entre valores de punto 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 System V, los primeros ocho argumentos de punto flotante se pasan a las funciones en orden, de xmm0 a xmm7.
Los valores de punto flotante se devuelven de las funciones en xmm0 y, si es necesario, en xmm1.
Todos los registros xmm están disponibles para usarse libremente; ninguno de ellos está reservado para el llamador.
Los valores de punto 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 esta pista) también permite declarar valores de punto 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, escribirás código para ayudar a un freelancer a comunicarse con un gerente de proyecto. Tu tarea es proporcionar algunas utilidades para calcular rápidamente tarifas diarias y mensuales, opcionalmente con un descuento dado.
Primero, establecemos algunas reglas entre el freelancer y el gerente de proyecto:
A veces, el freelancer ofrece aplicar un descuento sobre su tarifa diaria (por ejemplo, para sus clientes más leales o para clientes sin fines de lucro).
Los descuentos se modelan como números fraccionarios que representan porcentajes; por ejemplo, 25.0 (25%).
Estos son los sufijos de las instrucciones de punto 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 punto flotante a |
| roundss/roundsd a, b, c | redondea el número de punto flotante b según c y lo almacena en a |
| cvtss2si/cvtsd2si a, b | convierte el punto flotante b en el entero a |
| comiss/comisd a, b | hace una comparación entre a y b; excepción si es NaN
|
| ucomiss/ucomisd a, b | hace una comparación entre a y b; establece PF si es NaN
|
Estos son los números de control de redondeo en roundss y roundsd:
| Número | Modo |
|---|---|
| 0 | Más cercano |
| 1 | Suelo (hacia abajo) |
| 2 | Techo (hacia arriba) |
| 3 | Truncar |
Implementa una función llamada daily_rate para calcular la tarifa diaria dado como parámetro una tarifa por hora, como un número de punto 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 punto flotante de 64 bits.
Implementa una función apply_discount para calcular el precio después de un descuento.
Debe aceptar dos parámetros: el precio original y la tasa de descuento en porcentaje, ambos como un número de punto flotante de 64 bits.
apply_discount(150.0, 10.0)
// => 135.0
El valor devuelto siempre debe ser un número de punto flotante de 64 bits.
Implementa una función monthly_rate para calcular la tarifa mensual con descuento.
Toma dos parámetros, una tarifa por hora y el descuento en porcentaje, ambos como un número de punto 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 toma 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.