Ir ao conteúdo
  • Cadastre-se

Problema com calculo usando varial do tipo float


Posts recomendados

Bom dia, pessoa!

Estou com um problema referente a cálculos usando float.

Estou trabalhando em um dispositivo que possui uma plataforma específica, com um SDK específico (e isso é bem ruim) e, apesar de muitas funções serem as mesmas do C padrão, algumas não funcionam exatamente igual as do C padrão (e isso é mais ruim ainda), que é o caso da função "sprintf(...)".

Preciso transforma uma variável float em uma string. Eu sei que a função "sprintf(...)"  faz isso, porém, essa função não está funcionando para esse tipo de conversão, é um problema da plataforma mesmo, mas ela funciona bem para transformar um valor inteiro em uma string.

Bom... então, eu criei uma função que separa a parte inteira da parte decimal, converto a parte decimal em inteira e depois uso a função "sprintf(...)" para juntar as partes, colocar um ponto entre elas e armazenar em um buffer, logo, eu tenho o valor de float convertido para string.

Ex:

1 - Valor original: 12.5301f

2 - Parte inteira: 12

3 - Parte decimal: 0.5301f -> converte para inteiro: 5301

4 - uso o "sprintf ()" para concatenar as partes e converter para string: sprintf(buf, "%d.%d, 12, 5301);

5 - conteúdo do "buf" = "12.5301";

Até ai ok.

 

Para eu converter a parte decimal em inteiro eu fiz da seguinte forma:

1 - Multiplico a parte decimal por 10, para que a primeira casa decimal vire um valor inteiro;

2 - Armazeno e acumulo a parte inteira;

3 - desconto a parte inteira da parte decimal (deixando, novamente, apenas a parte decimal na variável) até que a variável tenha o valor "0.0";

 

Ex:

Decimal = 0.530100 * 10 = 5.301000  -> Decimal = 0.301000 Inteiro = 5    

Decimal = 0.301000 * 10 = 3.010000  -> Decimal = 0.010000 Inteiro = 53  

Decimal = 0.010000 * 10 = 0.100000  -> Decimal = 0.100000  Inteiro = 530 

Decimal = 0.100000 * 10 = 1.000000  -> Decimal = 0.000000  Inteiro = 5301

Fim -> Inteiro = 5301  

 

Só que isso não está funcionando, pois, sempre que multiplico o valor decimal por 10, o valor decimal está se alterando de uma forma imprevista.

 

Então resolvi fazer um teste no windows.

Usando o netbens como IDE eu implementei a mesma função e vi que ocorre o mesmo problema .

O problema é que, quando eu multiplico a parte decimal por 10, o processador  está "desaredondando" o valor decimal. Veja abaixo o mesmo processo anterior, mas agora  o que ocorre realmente, no windows:

Decimal = 0.530100 * 10 = 5.300998  -> Decimal = 0.300998  Inteiro = 5    

Decimal = 0.300998 * 10 = 3.009987  -> Decimal = 0.009987  Inteiro = 53  

Decimal = 0.009987  * 10 = 0. 099869  -> Decimal = 0. 099869 Inteiro = 530  

Decimal = 0. 099869  * 10 = 0. 998688  -> Decimal = 0. 998688 Inteiro = 5300...

... E assim vai até que chegue em algum ponto onde  "Decimal = 0.0". E isso vai bastante longe a ponto de dar overflow na variável inteiro deixando o resultado com um valor não esperado tipo: Inteiro = -232161348412479...

 

Minha duvida é:

1) Porque o processador "desaredonda" o valor decimal quando multiplico por 10?

2) Isso é um comportamento normal?

3) Eu estou fazendo algo errado?

4) Não estou levando em consideração algum comportamento de hardware ou SO?

 

Estou enviando o código para vocês testarem. Nó código existem alguns "prints" para ver os valores conforme for calculando a transformação. Fiquem a vontade para mudarem os "prints" de lugar, mas só peço que fiquem atentos a dois "prints" que destaquei, no código, e quando impressos apresentam setas apontando para valor "printado", são esses "prints" que mostram o momento do "desaredondamento".

 

Agradeço desde já pela a ajuda.

TesteFloat.c

Link para o comentário
Compartilhar em outros sites

Olá, Roger.

 

A variável inteiro estava ficando tão grande que dava overflow, ou seja, ficava tão grande que voltava pra zero. Além disso, seu código imprime a variável inteiro (que é do tipo unsigned int) com %d na string que você passa para a função printf, e %d é usado para imprimir valores do tipo int (com sinal), e é por isso que o seu programa imprime valores negativos. Para resolver esses dois problemas, troque isso:

unsigned int inteiro = 0;

por isso:

unsigned long long int inteiro = 0;

para que a variável possa armazenar números maiores. Além disso, troque:

printf("inteiro: %d\n\n", inteiro);

por:

printf("inteiro: %llu\n\n", inteiro);

para imprimir valores do tipo unsigned long long int. Agora resta alterar o código que de fato grava o número na string. Veja que no seu código você faz isso da seguinte forma:

sprintf(buf, "%d.%d", inteiro, inteiro);

Oras, aqui você está gravando a variável inteiro (que a esse ponto do programa contém a parte fracionária) duas vezes, separando-os por um ponto. Isso não está certo, você deve primeiro gravar inteiro na string seguido de um ponto antes de entrar no laço de repetição (gravando assim a parte inteira e o separador decimal), e depois do laço de repetição você tem que gravar essa variável de novo (agora com a parte fracionário) ao final da string. Isso é feito da seguinte forma: você declara uma variável inteira, vamos chamá-la de deslocamento, essa variável conterá o número de caracteres que foram gravados na string. Assim, acrescente isso logo antes do laço de repetição:

int deslocamento = sprintf(buf, "%d.", (int)f);

Isso vai gravar a parte inteira na string. Depois, no final, troque isso:

sprintf(buf, "%d.%d", inteiro, inteiro);

por isso:

sprintf(buf + deslocamento, "%llu", inteiro);

para gravar a parte fracionária depois dos deslocamento caracteres que foram gravadas antes de entrar no laço de repetição. A função agora deve estar assim:

void ConvertFloatToString(float f, char *buf)
{
    float temp;
    float ret;
    float num;
    unsigned long long int inteiro = 0;

    temp = (int)f; // Carrega a variavel temp com a parte intira do valor;
    temp = f - temp; // Carrega temp com a parte decimal do valor;

    printf("Parte decimal do valor a ser convertido: %f\n\n\n", temp);

    int deslocamento = sprintf(buf, "%d.", (int)f);
    do
    {
        inteiro = inteiro * 10; // Multipica por 10 para poder fazer o acumulo das numeros decimais;

        num = (int)temp; // a variavel "num" recebe apenas a parte inteira do numero;
        ret = temp - num; // esse calculo retira a parte inteira e atribua somente a parte decimal a variavel "ret";

        //=========================================================================
        printf("temp1: %f <----------\n", temp);
        temp = ret * 10; // Multipica por 10 para transformar a primeira casa decimal, após a virgula, em um inteiro;
        printf("temp2: %f <----------\n", temp);
        //=======================================================================

        inteiro = inteiro + num; // Esse calculo acumula os valores decimais transformados em inteiro;

        printf("ret: %f\n", ret);
        printf("num: %f\n", num);
        printf("temp: %f\n", temp);
        printf("inteiro: %llu\n\n", inteiro);
    }while(ret > 0.0f); // Faca isso enquanto o valor decimal for maior do que 0,0;

    /*
     * Aqui eu tranformo os dis valores inteiros eum uma string representando o valor
     * original de float, e coloco um ponto entre os dois inteiros;
     */
    sprintf(buf + deslocamento, "%llu", inteiro);
}

Rodando esse código na minha máquina, obtenho como resposta final buf: 12.5300997895426670592 sendo que o valor que foi passado para dentro da função é 12.5301f, e isso acontece porque números de ponto flutuante são representações binárias de números decimais, então introduz-se um pequeno erro ao armazenar um número em formato de ponto flutuante. É por isso que não se aconselha a usar o operador != diretamente para verificar se um float é diferente de outro, recomenda-se verificar se a diferença entre os dois floats é maior que um limiar, pois dessa forma você está considerando o erro que ocorre em números de ponto flutuante.

  • Curtir 1
Link para o comentário
Compartilhar em outros sites

Olá, danieltm64!

 

Cara... Valeu pelas dicas!

 

Com relação a comparação entre floats, vou ter que arranjar outra forma de fazer isso, então!?

Pelo que eu andei pesquisando, referente a variáveis de tipo float, parece que tem umas particularidades por causa de arredondamentos que são feitos, por causa dos erros que você mencionou.

 

 Bom... Vou dar uma pesquisada pra ver como seria o melhor jeito de fazer o que eu preciso. Mas seus esclarecimentos já me ajudaram.

 

 Obrigado pela força!

Link para o comentário
Compartilhar em outros sites

Visitante
Este tópico está impedido de receber novas respostas.

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...

Ebook grátis: Aprenda a ler resistores e capacitores!

EBOOK GRÁTIS!

CLIQUE AQUI E BAIXE AGORA MESMO!