Um número de vírgula flutuante é um número que pode ter uma parte fracionária, como -1.23 ou 3.1415.
Para representar um número destes como uma sequência de bytes, o x86-64 usa a norma IEEE-754. Os dois formatos mais comuns nesta norma são os números de vírgula flutuante de precisão simples e de precisão dupla. O x86-64 suporta ambos.
Um número de vírgula flutuante de precisão simples ocupa 32 bits, enquanto um de precisão dupla ocupa 64 bits.
No x86-64, existem 16 registos dedicados à aritmética de vírgula flutuante, com os nomes xmm0 a xmm15.
Esses registos têm pelo menos 128 bits, mas os processadores recentes aumentam isso para 256 ou 512 bits. No entanto, na prática, a maioria dos números de vírgula flutuante usa apenas os 32 ou 64 bits mais baixos, consoante a precisão pretendida.
Existe uma família de instruções para converter entre formatos de números: cvtxx2yy.
Tanto xx como yy não são literais, mas sim marcadores:
xx é um marcador para o tipo do operando de origem.yy é um marcador para o tipo do operando de destino.Este marcador pode assumir muitas formas, entre elas:
| marcador | significado |
|---|---|
si |
inteiro com sinal |
ss |
vírgula flutuante de 32 bits |
sd |
vírgula flutuante de 64 bits |
Por exemplo:
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
Repara que é possível converter um inteiro de 32 bits num número de vírgula flutuante de 64 bits, ou um inteiro de 64 bits num número de vírgula flutuante de 32 bits.
Do mesmo modo, também é possível converter um número de vírgula flutuante de 32 bits num inteiro de 64 bits, ou um de 64 bits num inteiro de 32 bits.
Por outras palavras, o tamanho dos dois operandos numa cvtxx2yy não tem de ser o mesmo:
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 o número de vírgula flutuante não pode ser expresso exatamente como um inteiro, a cvtxx2si arredonda o número de acordo com o controlo de arredondamento num registo especial chamado mxcsr.
Isto pode ser um arredondamento para o mais próximo, para baixo, para cima, ou um truncamento.
Não há nenhum valor predefinido garantido à entrada da função.
Repara que um resultado exato não é arredondado e, por isso, não é afetado pelo controlo de arredondamento.
Por exemplo, 42.0 seria sempre convertido em 42.
Existe uma família semelhante de instruções com o formato cvttxx2si (com um t a mais) que trunca sempre o resultado.
A família de instruções roundxx converte um número de vírgula flutuante do operando de origem num número de vírgula flutuante arredondado, guardado no operando de destino.
Aqui, xx é um marcador para o tipo dos operandos, como habitualmente: ss para precisão simples e sd para precisão dupla.
Estas instruções recebem um número constante como terceiro operando. Este número indica o modo de arredondamento:
| Número | Modo |
|---|---|
| 0 | Mais próximo |
| 1 | Para baixo |
| 2 | Para cima |
| 3 | Truncar |
Por exemplo:
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 estas instruções produzem um resultado exato, podem ser usadas como preparação para uma cvtxx2si.
Muitas operações com números de vírgula flutuante usam instruções com um nome semelhante ao do seu equivalente para inteiros, mas acrescentando o marcador necessário (ss ou sd) no fim.
Estas instruções são de dois operandos, com a semântica habitual, mesmo as que fazem multiplicação ou divisão:
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)
As instruções para mover de um registo XMM para outro são movss ou 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
Não é possível mover um valor imediato (um inteiro constante com sinal) diretamente para um registo xmm.
Este número tem primeiro de ser movido para um registo de uso geral e depois convertido com 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
Há dois grupos de instruções para comparar dois números de vírgula flutuante.
Têm a forma comixx e ucomixx, em que xx é ss ou sd.
Ambas definem os flags em rflags como habitualmente, seguindo a mesma semântica que cmp.
A diferença entre elas é que a ucomixx define o parity flag (PF) se um dos dois números de vírgula flutuante tiver um valor NaN, enquanto a comixx gera uma exceção neste caso.
A comparação entre valores de vírgula flutuante é sem sinal:
ucomisd xmm0, xmm1
ja .greater ; 'ja' must be used for "greater", not 'jg'
jb .lesser ; 'jb' must be used for "lesser", not 'jl'
Na System V ABI, os primeiros oito argumentos de vírgula flutuante são passados às funções por ordem, de xmm0 a xmm7.
Os valores de vírgula flutuante são devolvidos pelas funções em xmm0 e, se necessário, em xmm1.
Todos os registos xmm estão disponíveis para uso livre, nenhum deles está reservado para o chamador.
Os valores de vírgula flutuante podem ser definidos em memória seguindo a mesma semântica que os inteiros do mesmo tamanho. O NASM (The Netwide Assembler, o assembler usado nesta track) também permite declarar valores de vírgula flutuante em notação 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
Neste exercício, vais escrever código para ajudar um freelancer a comunicar com um gestor de projeto. A tua tarefa é fornecer alguns utilitários para calcular rapidamente tarifas diárias e mensais, opcionalmente com um desconto.
Vamos primeiro estabelecer algumas regras entre o freelancer e o gestor de projeto:
Por vezes, o freelancer oferece-se para aplicar um desconto na sua tarifa diária (por exemplo, para os clientes mais fiéis ou para clientes sem fins lucrativos).
Os descontos são modelados como números fracionários que representam percentagens, por exemplo, 25.0 (25%).
Estes são os sufixos das instruções de vírgula flutuante:
| Sufixo | Tamanho do operando |
|---|---|
ss |
32 bits |
sd |
64 bits |
Estas são algumas das instruções mencionadas neste conceito:
| Instrução | Descrição |
|---|---|
| movss/movsd a, b | copia b para 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 o número inteiro b em vírgula flutuante a |
| roundss/roundsd a, b, c | arredonda o valor de vírgula flutuante b de acordo com c e guarda-o em a |
| cvtss2si/cvtsd2si a, b | converte o valor de vírgula flutuante b em número inteiro a |
| comiss/comisd a, b | faz uma comparação entre a e b, com exceção se for NaN
|
| ucomiss/ucomisd a, b | faz uma comparação entre a e b, define PF se for NaN
|
Estes são os números de controlo de arredondamento em roundss e roundsd:
| Número | Modo |
|---|---|
| 0 | Mais próximo |
| 1 | Para baixo (floor) |
| 2 | Para cima (ceil) |
| 3 | Truncar |
Implementa uma função chamada daily_rate para calcular a tarifa diária, recebendo como parâmetro uma tarifa horária, na forma de um número de vírgula flutuante de 64 bits.
O contrato define que um dia tem 8 horas faturáveis.
daily_rate(60)
// => 480.0
A tarifa diária devolvida deve ser um número de vírgula flutuante de 64 bits.
Implementa uma função apply_discount para calcular o preço depois de aplicar um desconto.
Deve aceitar dois parâmetros: o preço original e a taxa de desconto em percentagem, ambos na forma de um número de vírgula flutuante de 64 bits.
apply_discount(150.0, 10.0)
// => 135.0
O valor devolvido deve ser sempre um número de vírgula flutuante de 64 bits.
Implementa uma função monthly_rate para calcular a tarifa mensal com desconto.
Recebe dois parâmetros, uma tarifa horária e o desconto em percentagem, ambos na forma de um número de vírgula flutuante de 64 bits.
monthly_rate(77.0, 10.5)
// => 12130
A tarifa mensal devolvida deve ser arredondada por excesso (para cima) para o número inteiro de 64 bits mais próximo.
Implementa uma função days_in_budget que recebe como parâmetros:
Esta função deve devolver o número de dias de trabalho completos que o orçamento cobre.
days_in_budget(20000, 80, 11.0)
// => 35
O número de dias devolvido deve ser arredondado por defeito (para baixo) para o número inteiro de 32 bits mais próximo.
Inscreve-te no Exercism para aprenderes e dominares x86-64 Assembly com 22 conceitos130 exercícios, e mentoria humana real, tudo grátis.