fredericopissarra Posted April 7, 2019 at 04:45 PM Share Posted April 7, 2019 at 04:45 PM Lidar com ponto flutuante, além dos problemas com exatidão, tem outro entrave: Algumas operações são bem mais lentas do que as equivalentes para tipos inteiros. Mas, infelizmente, nem sempre podemos nos livrar da aritmética em ponto flutuante. O que podemos fazer é aproveitar algumas características da estrutura dos tipos (especialmente float e double) e tentarmos tornar algumas operações mais rápidas. Por exemplo, já citei o “macete” de retornar -1, 0 ou 1 de acordo com a comparação de dois valores inteiros: 1 2 int comp1( int a, int b ) { return (a > b) - (a < b); } E, é claro, isso aplica-se á ponto flutuante também: 1 2 int comp2( float a, float b ) { return (a > b) - (a < b); } Embora existam alguns problemas com o código acima, devido à precisão, vamos assumir que essas comparações possam ser feitas sem problemas e darmos uma olhada nos códigos gerados pelo compilador para x86-64: 1 2 3 4 5 6 7 8 9 comp2: comp1: xor eax, eax xor eax, eax ucomiss xmm0, xmm1 cmp edi, esi seta al setl dl xor edx, edx setg al ucomiss xmm1, xmm0 movzx edx, dl seta dl sub eax, edx sub eax, edx ret ret Reparem que o código que lida com ints faz apenas uma comparação, mas o código dos floats faz duas e a instrução ucomiss é lenta, em relação à cmp. No resto, as funções são idênticas. Agora, aqui vai o macete interessante: A estrutura de um float nos garante, se tivermos valores sub-normais ou normalizados, que os valores inteiros correspondentes a eles (não convertido a partir de) possam ser comparados, como inteiros, sem problemas. Ou seja, essas duas comparações são “idênticas”: 1 2 3 4 5 6 7 // a e b são floats // compara floats. (a > b) - (a < b); // compara ints. (*(int *)&a > *(int *)&b) - (*(int *)&a < *(int *)&b); É bom lembrar que um valor em ponto flutuante é uma fração onde apenas o numerador e o expoente da escala estão armazenados (bem como o sinal), ou seja: Estrutura de um float Onde a fração f e o expoente da escala, e, são inteiros positivos. Aparentemente essa comparação com float como se fossem inteiros não funciona porque a representação de valores negativos em complemento 2 não bate, não é? Acontece que a estrutura foi feita justamente para isso! A posição dos campos da estrutura permitem a comparação “como se” os valores correspondentes à estrutura fossem inteiros sinalizados! Isso é fácil de provar, mas levaria muito tempo e este texto ficaria grande. No entanto, eis um exemplo: Considere que tenhamos dois valores: 9.0 e 10.0. Nas estruturas dos valores binários, antes do escalonamento, 9 é 0b1.001 e 10 é 0b1.01. A escala deslocará o ponto binário exatamente 3 bits para a direita, ou seja, num float, e=130. a fração, excluindo o 1 implícito terá f como 0b00100000000000000000000 e 0b01000000000000000000000, para 9 e 10, respectivamente. Os valores correspondentes às estruturas serão, para 9, s=0, e=130, f=0x100000; e para o 10 apenas fmuda para 0x200000. Ou seja, 9 é codificado como 0x41100000 e 10 como 0x41200000. Mas, e quanto a -9 e -10? Da mesma maneira, mas apenas o bit de sinal muda e teremos -9 como 0xc1100000 e -10 como 0xc1200000, onde, é claro, numa comparação sinalizada, 0xc1100000 é maior que 0xc1200000. A mesma lógica aplica-se aos tipos double, só que eles têm 64 bits e, então, deve-se usar o tipo long long ou int64_t. Mas, o macete pode não funcionar para o tipo long double porque ele tem 10 bytes de tamanho e, embora, por alinhamento, o compilador reserve 16, possivelmente os 6 bits que faltam podem ter “lixo”… Mas, você pode usar o macete com os tipos __float128 e __int128, se seu compilador suportá-los. Como ficamos? Comparações entre valores em ponto flutuante podem ser feitos pelo valor inteiro, equivalente as suas estruturas. Ou seja, no exemplo das rotinas de comparação lá em cima, poderíamos fazer: 1 2 3 4 5 // compara floats. int r = comp2(a, b) ... // compara estrutura de floats. int r = comp1(*(int *)&a, *(int *)&b); A segunda opção é mais rápida… Atenção!!! O macete funciona se os valores não forem NaNs ou Infs. Nesses casos, mesmo a comparação em ponto flutuante é projetada para “falhar”. Aliás, a instrução ucomiss, lida com comparações escalares de precisão simples (ss = single scalar) e o “u” significa “unordered”, que quer dizer que se um ou mais dos valores for NaN os flags serão ajustado de acordo (este é o único caso onde PF será 1 – e UCOMISS sempre zera SF e OF. É como se a comparação fosse não sinalizada!). Um outro detalhe é que isso só funciona se estivermos lidando com ponto flutuante, binário, no padrão IEEE-754. Sim, existem tipos “decimais” no IEEE 754, mas a biblioteca padrão não tem um bom suporte… Existem, também, plataformas que não usam o padrão IEEE, mas são extremamente raras hoje em dia… Fonte: https://wp.me/pudbF-1Fo Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.