fredericopissarra Posted November 1, 2019 at 11:59 PM Share Posted November 1, 2019 at 11:59 PM Artigo original: https://is.gd/28eIsS Num outro artigo (este aqui) mostrei como obter o valor representado pela estrutura de um tipo ponto flutuante (float) e vice-versa, mas não mostrei um código fonte de exemplo. Eis aqui um, mas com falhas, porque não leva em consideração valores muito grandes, muito pequenos, subnormais, infinitos e NaNs. Ele converte um valor em ponto fixo (limitado), onde a parte inteira i e a parte fracionária f do valor é fornecida à função. Como está a parte fracionária jamais poderia ser 0.01 ou 0.00034, etc. Apenas valores como: 3.14, 100.4003, etc podem ser fornecidos. A rotina converte o valor f na componente fracionária e junta as duas numa grande variável de 64 bits, deslocando os bits até isolar o bit de mais alta ordem no bit 32 (o 1.0. implícito num float) e daí obtemos o valor em ponto flutuante. Deixo o código para a análise de vocês: #include <stdio.h> #include <assert.h> // União com a estrutura de um float, para faciliar // as coisas... union fp_u { float f; struct { unsigned int m:23; unsigned int e:8; unsigned int s:1; } __attribute__((packed)); }; // Calcula o log, na base 10, de x. static int log10_( unsigned int x ) { int n = 0; while ( x >= 10 ) { x /= 10; n++; } return n; } // Calcula 10^x (x precisa ser menor que 10). static unsigned long long pow10_( unsigned int x ) { unsigned long long n = 1; assert( x < 10 ); while ( x-- ) n *= 10; return n; } // Converte um ponto fixo no formato (i,f) para float. // Essa rotina tem multiplas falhas: // A parte fracionária não permite valores como 0.01, por exemplo. // Não verifico por subnormais, nans e infinitos. // Não trato valores negativos. float fixed2float( unsigned int i, unsigned int f ) { unsigned int m; unsigned long long n; int e = 0; union fp_u fp = { .f = 0.0 }; // OK... 0.0f é apenas 0x00000000. // 0.0 é um caso especial. if ( i != 0 && f != 0 ) { // Precisamos transformat a parte fracionária na represetação binária // do número fixo. Poderíamos fazer isso com ponto fluautuante, mas o objetivo // aqui é evitar ponto flutuante, não é? // // A mágica aqui é que na representação de número fixo (iiii.ffff) a parte fracionária // é (f / 2³²), mas temos apenas f inteiro. Então, coloco f na parte inteira (multiplicando por // 2³², e divido pela grandeza do valor (10^(log(f)+1)). // // Isso garante que m terá apeans 32 bits de tamanho final e todo o cálculo é inteiro (nada de // ponto flutuante aqui). m = ( ( unsigned long long )f << 32 ) / ( pow10_( log10_( f ) + 1 ) ); // Agora que temos a representação fracionária correta, basta colocar os dois valores // nas posições corretas. A parte inteira são os 32 bits superiores, a parte fracionária, os // 32 bits inferiores. n = m + ( ( unsigned long long )i << 32 ); // Um float normalizado sempre tem 1 implícito. Assim, se qualquer um dos 31 bits supeiores for 1, // então temos que fazer deslocamentos para a direita, senão, para a esquerda, até que os 32 bits // superiores contenha apenas 1. // // OBS: ~0ULL << 33 == 0xfffffffe00000000ULL, // ~0ULL << 32 == 0xffffffff00000000ULL e, é claro, // 1ULL << 32 = 0x0000000100000000ULL. if ( n & ( ~0ULL << 33 ) ) { // Ao deslocar para a direita, incrementa o expoente. // OBS: Dá pra melhorar isso contanto a quantidade de 'leading zeros' e deslocando a // quantidade necessária de uma vez só... while ( ( n & ( ~0ULL << 32 ) ) != ( 1ULL << 32 ) ) { n >>= 1; e++; } } else { // Ao descolar para a esquerda, decrementa o expoente. // OBS: Dá pra melhorar isso contanto a quantidade de 'leading zeros' e deslocando a // quantidade necessária de uma vez só... while ( ! ( n & ( 1ULL << 32 ) ) ) { n <<= 1; e--; } } fp.m = n >> 9; // Joga fora os 9 bits inferiores (precisams de apenas 23 bits). fp.e = 127+e; // Calcula o expoente do float. fp.s = 0; // não lido com sinal, por enquanto! // Acerta o arredondamento. Precisamos arredondar a "mantissa" se o 9º bit for 1. if ( n & 0x100 ) fp.m++; // Se a "mantissa" chegou a zero, temos que delocar mais um bit, decrementando o expoente. if ( ! fp.m ) fp.e--; } return fp.f; } // Um pequeno teste. int main( void ) { printf( "%.20f = %.20f\n", 3.1416f, fixed2float( 3, 1416 ) ); } Compilando e executando: $ cc -o test test.c $ ./test 3.14159989356994628906 = 3.14159989356994628906 Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.