Um número de ponto flutuante é um número que pode ter uma parte fracionária, como -1.23 ou 3.1415.
Para representar um número assim como uma sequência de bytes, o x86-64 usa o padrão IEEE-754. Os dois formatos mais comuns desse padrão são os números de ponto flutuante de precisão simples e de precisão dupla. O x86-64 dá suporte aos dois.
Um número de ponto flutuante de precisão simples tem 32 bits de largura, enquanto um de precisão dupla tem 64 bits.
No x86-64, há 16 registradores dedicados à aritmética de ponto flutuante, chamados de xmm0 a xmm15.
Esses registradores têm pelo menos 128 bits de largura, mas processadores recentes aumentam isso para 256 ou 512 bits. Na prática, porém, a maioria dos números de ponto flutuante usa apenas os 32 ou os 64 bits mais baixos, dependendo da precisão desejada.
Existe uma família de instruções para converter entre formatos de número: cvtxx2yy.
Tanto xx quanto yy não são literais, mas espaços reservados:
xx é um espaço reservado para o tipo do operando de origem.yy é um espaço reservado para o tipo do operando de destino.Esse espaço reservado pode assumir muitas formas, entre elas:
| placeholder | meaning |
|---|---|
si |
inteiro com sinal |
ss |
ponto flutuante de 32 bits |
sd |
ponto 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
Repare que é possível converter um inteiro de 32 bits em um ponto flutuante de 64 bits ou um inteiro de 64 bits em um ponto flutuante de 32 bits.
Da mesma forma, também é possível converter um ponto flutuante de 32 bits em um inteiro de 64 bits ou um ponto flutuante de 64 bits em um inteiro de 32 bits.
Em outras palavras, o tamanho dos dois operandos em uma cvtxx2yy não precisa 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 ponto flutuante não puder ser expresso exatamente como um inteiro, cvtxx2si arredonda o número de acordo com o controle de arredondamento em um registrador especial chamado mxcsr.
Isso pode ser um arredondamento para o mais próximo, para baixo, para cima ou um truncamento.
Não há um padrão garantido na entrada da função.
Repare que um resultado exato não é arredondado e, portanto, não é afetado pelo controle de arredondamento.
Por exemplo, 42.0 seria sempre convertido para 42.
Há uma família semelhante de instruções com o formato cvttxx2si (com um t a mais) que sempre trunca o resultado.
A família de instruções roundxx converte um ponto flutuante no operando de origem em um ponto flutuante arredondado, armazenado no operando de destino.
Aqui, xx é um espaço reservado para o tipo dos operandos, como de costume: ss para precisão simples e sd para precisão dupla.
Essas instruções recebem um número constante como terceiro operando. Esse número indica o modo de arredondamento:
| Number | Mode |
|---|---|
| 0 | Mais próximo |
| 1 | Piso (para baixo) |
| 2 | Teto (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 essas instruções produzem um resultado exato, elas podem ser usadas como preparação para uma cvtxx2si.
Muitas operações com números de ponto flutuante usam instruções com um nome semelhante ao equivalente para inteiros, mas acrescentando o espaço reservado necessário (ss ou sd) no final.
Essas instruções têm dois operandos, com a semântica de sempre, inclusive 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 registrador 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 imediato (um inteiro constante com sinal) diretamente para um registrador xmm.
Esse número primeiro precisa ser movido para um registrador de uso geral e depois convertido 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
Há dois grupos de instruções para comparar dois números de ponto flutuante.
Elas têm a forma comixx e ucomixx, em que xx é ss ou sd.
Ambas definem flags em rflags como de costume, seguindo a mesma semântica do cmp.
A diferença entre elas é que ucomixx define a parity flag (PF) se um dos dois números de ponto flutuante tiver um valor NaN, enquanto comixx gera uma exceção nesse caso.
A comparação entre valores de ponto 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 ABI System V, os oito primeiros argumentos de ponto flutuante são passados para funções em ordem, de xmm0 a xmm7.
Valores de ponto flutuante são retornados pelas funções em xmm0 e, se necessário, em xmm1.
Todos os registradores xmm estão livres para uso, nenhum deles é reservado para quem chama.
Valores de ponto flutuante podem ser definidos na memória seguindo a mesma semântica dos inteiros do mesmo tamanho. O NASM (The Netwide Assembler, o montador usado por esta trilha) também permite declarar valores de ponto 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, você vai escrever código para ajudar uma pessoa freelancer a se comunicar com um gerente de projeto. Sua tarefa é fornecer alguns utilitários para calcular rapidamente as taxas diária e mensal, opcionalmente com um desconto dado.
Primeiro, estabelecemos algumas regras entre a pessoa freelancer e o gerente de projeto:
Às vezes, a pessoa freelancer oferece aplicar um desconto sobre sua taxa diária (por exemplo, para clientes mais fiéis ou clientes sem fins lucrativos).
Os descontos são modelados como números fracionários que representam porcentagens, por exemplo, 25.0 (25%).
Estes são os sufixos das instruções de ponto 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 inteiro b para ponto flutuante a |
| roundss/roundsd a, b, c | arredonda o ponto flutuante b de acordo com c e armazena em a |
| cvtss2si/cvtsd2si a, b | converte o ponto flutuante b para o inteiro a |
| comiss/comisd a, b | faz uma comparação entre a e b, exceção se NaN
|
| ucomiss/ucomisd a, b | faz uma comparação entre a e b, define PF se NaN
|
Estes são os números de controle de arredondamento em roundss e roundsd:
| Número | Modo |
|---|---|
| 0 | Mais próximo |
| 1 | Para baixo |
| 2 | Para cima |
| 3 | Truncar |
Implemente uma função chamada daily_rate para calcular a taxa diária a partir de uma taxa horária recebida como parâmetro, como um número de ponto flutuante de 64 bits.
O contrato define que um dia tem 8 horas faturáveis.
daily_rate(60)
// => 480.0
A taxa diária retornada deve ser um número de ponto flutuante de 64 bits.
Implemente uma função apply_discount para calcular o preço após um desconto.
Ela deve aceitar dois parâmetros: o preço original e a taxa de desconto em porcentagem, ambos como um número de ponto flutuante de 64 bits.
apply_discount(150.0, 10.0)
// => 135.0
O valor retornado deve ser sempre um número de ponto flutuante de 64 bits.
Implemente uma função monthly_rate para calcular a taxa mensal com desconto.
Ela recebe dois parâmetros: uma taxa horária e o desconto em porcentagem, ambos como um número de ponto flutuante de 64 bits.
monthly_rate(77.0, 10.5)
// => 12130
A taxa mensal retornada deve ser arredondada para cima (teto) para o inteiro de 64 bits mais próximo.
Implemente uma função days_in_budget que recebe como parâmetros:
Essa função deve retornar o número de dias completos de trabalho que o orçamento cobre.
days_in_budget(20000, 80, 11.0)
// => 35
O número de dias retornado deve ser arredondado para baixo (piso) para o inteiro de 32 bits mais próximo.
Crie sua conta no Exercism para aprender e dominar x86-64 Assembly com 22 conceitos130 exercícios e mentoria humana de verdade, tudo de graça.