Cuidado com números em Ponto Flutuante

Quanto seria (200*0,7) + 200? 340 você diria? Então você deve ler este artigo. Dependendo da situação esta operação matemática não resultará em 340. Absurdo? Quem programa em linguagem de programação C deve ficar atento!

[ Hits: 126.384 ]

Por: Elgio Schlemer em 20/03/2008 | Blog: https://elgio.prof.nom.br/~elgio


Conclusão



Novamente o pequeno programa em C:

#include <stdio.h>

int main()
{
    int a;
    a = 200;
    a = (a * 0.7) + a;
    printf("O valor de a eh %d\n", a);
}

Agora dá para entender. Ao realizar 0.7 * 200, na verdade foi feito, lá nos bastidores "bitianos" a operação 0.69999999999999995559 * 200 e isto resulta em 139.99999999999999111800.

Ao somar com 200, o valor final é 339.99999999999999111800! Mesmo que isto seja praticamente 340, como está sendo atribuído ao a que é um inteiro, as casas decimais são sumariamente perdidas e o número fica 339!!!

Isto não ocorreria se variável a fosse do tipo double, pois neste caso o C realiza arredondamentos. Isto também não ocorre no PHP pelo mesmo motivo. Ocorre em C porque ele converte double para int removendo as casas decimais, por mais doloroso que isto seja!

Deve-se registrar aqui que tal "bug" não ocorre em compiladores gcc de 64 bits.
Página anterior    

Páginas do artigo
   1. Pequeno programa em C
   2. Entendendo conversões implícitas em C
   3. Conversão de ponto flutuante para inteiro
   4. Nem tudo é representável
   5. Conclusão
Outros artigos deste autor

Estrutura do Iptables

Estrutura do IPTables 2: a tabela nat

Programação com números inteiros gigantes

Guerra Infinita, uma análise da Ciência da Computação

Introdução a criptografia

Leitura recomendada

Sinais em Linux

Linguagem C - Funções Variádicas

Como funcionam os alocadores de memória do STD C?

A duplicação do buffer de saída na chamada de sistema fork() do Linux

Criando aplicativos para o iPhone no Linux (sem Xcode e MacOS X)

  
Comentários
[1] Comentário enviado por tenchi em 20/03/2008 - 16:14h

Mais um sensacional Artigo do Prof. Elgio. Vai pros favoritos pra eu nunca mais esquecer! ;-)

[2] Comentário enviado por jmsandy em 20/03/2008 - 17:16h

Opa, blza?

Este episódio não acontece somente com este produto. Pegando o seu exemplo 200 x 0.7, o fator 0.7, quase todos os seus múltiplos o valor é arredondado para 1 a menos.
Achei bastante interessante e testei aqui no trabalho em um compilador for Windows quando chegar em casa vou testar no Linux para tirar a tema. Mais observação muito interessante. Em determinados compiladores, como o dá Borland, existe um erro quanto a ponto flutuante que voce tem que declarar um prototipo de uma função para ele não acontecer. E támbém o flush(stdin) para limpar o buffer(ou alguma coisa parecida é que não me recordo muito bem).
Está de parabéns pela observação.

José Mauro Sandy


[3] Comentário enviado por antonioclj em 20/03/2008 - 17:47h

Mas isto vai acontecer mesmo. Você declarou um inteiro e faz uma multiplicação com ponto flutuante, vai dar inteiro na resposta. Você tem que usar o modificador float e não int. Bom isto é para os porgramadores de plantão.

[4] Comentário enviado por elgio em 20/03/2008 - 17:51h

Oi Antonio.

Sabemos disto.
Mas acho que tu não entendeu direito o FOCO que eu explorei.

No caso a multiplicacao 0.7*200 devia resultar em um inteiro redondo, assim como 0.6 * 100 que resulta em 60.

Não tem poblema algum misturar int com doubles ou float em C, desde que VOCÊ SAIBA o que está fazendo. Eu uso muito isto (mesmo que raramente saiba o que estou fazendo :-D)

[]'s

[5] Comentário enviado por f_Candido em 20/03/2008 - 18:55h

Simplesmente Perfeito.

[6] Comentário enviado por gabrield em 20/03/2008 - 22:18h

Alta qualidade!!!

Parabéns cara pelo artigo!

[7] Comentário enviado por countercraft em 21/03/2008 - 00:42h

Não sei programar em C, mas fiquei curioso e compilei o código acima no GCC e ele retornou 340 mesmo :)

[8] Comentário enviado por pedroarthur.jedi em 21/03/2008 - 09:39h

Você já leu algo acerca de erros de representação?
Qualquer livro de calculo numérico tem...

[9] Comentário enviado por rui_alex em 21/03/2008 - 11:03h

Ta bom ta.

[10] Comentário enviado por elgio em 21/03/2008 - 11:44h

Gabriel!.

Em arquiteturas 64 bits este erro não ocorre.
Não sei exatamente PORQUE NÃO OCORRE, pois testei em um servidor 64 bits que tenho acesso e vi que os arredondamentos SÃO FEITOS!!

Inclusive COM MESMA versão de GCC.

Meu notebook, 32 bits, GCC 4.1.2.
Tamanho de um double: 8 bytes
0.7 = 0.6999999999999999555910790149937383830547

Em um servidor 64 bits, GCC 4.1.2
Tamanho de um double: 8 bytes
0.7 = 0.6999999999999999555910790149937383830547

Mas no 64 bits o GCC informa CORRETAMENTE 340 como tu falou!

Se alguém sabe o motivo, poderia contribuir. Este GCC para 64 bits é o PRIMEIRO COMPILADOR C que faz o cálculo CORRETO! O Dev++, Borland e GCC 32 bits fazem errado.

[11] Comentário enviado por elgio em 21/03/2008 - 11:45h

pedroarthur.jedi: sim, eu conheço bem os erros de representação.

Tanto que no artigo eu descrevo isto como um erro.
O que ocorreu neste caso NÃO É UM MISTÉRIO. Só escrevi o artigo para que todos que programem em C tenham muito cuidado com estes erros.

[12] Comentário enviado por countercraft em 21/03/2008 - 16:00h

Realmente, fiz este teste no Arch 64, com GCC 4.2.3.

[13] Comentário enviado por rui_alex em 21/03/2008 - 18:22h

[There was strong agreement in the C89 Committee that floating values should truncate toward zero when converted to an integer type, the specification adopted in the Standard.]

[Many results are exact or correctly rounded when computed with twice the number of digits of
precision as the data.]
-c99 Rationale-

Na arquitectura 64 bits suponho que os floats sejam pelo menos 8 bytes?

[14] Comentário enviado por elgio em 21/03/2008 - 20:30h

Bom...

O que eu descobri com um sizeof foi:

32 bits: int, long int e float em 32 bits. double em 64 bits, "long double" com 12 bytes (96 bits). Mas isto o que o gcc retorna, não quer dizer que seja o que a ULA e o co-processador suporta (alguém bom em Hardware ai?)

No 64 bits, MESMA VERSÃO de gcc, obtive: int e float em 32 bits, double em 64 bits e "long double" com 16 bytes (128 bits).

Mas é estranho, pois em ambas o problema de PRECISÃO EXISTE, isto é, 0.7 não é representável com PRECISÃO.

Por que diabos o gcc para 64 bits realiza algum ARREDONDAMENTO?

[15] Comentário enviado por removido em 22/03/2008 - 10:34h

interessante,
quem sabe faz ao vivo !!!!!!!!!!

[16] Comentário enviado por luizpetiz em 22/03/2008 - 14:45h

É... legalzinho!
Psiu! Tô falando assim porque o cara é meu professor na ulbra. Não posso dizer que é um baita artigo e só um cara como ele poderia dedicar-se a esses assuntos e tal.., que ele é fera mesmo, se não ele se convence...

[17] Comentário enviado por GilsonDeElt em 22/03/2008 - 19:43h

legal esse trem!

ainda tô começando na programação, e num sabia dessa...

[18] Comentário enviado por estevao90 em 22/03/2008 - 21:01h

mt bom...
meu professor sempre alerta a gente quando a esse mistério do C
o negócio é: se for precisar de ponto flutuante, coloca todas variáveis envolvidas como float, por exemplo, facilita a vida! rsrs

[19] Comentário enviado por brunojbpereira em 22/03/2008 - 22:21h

tá uma coisa a se levar em conta na hora de escolher a representação numérica. parabéns pelo artigo.

[20] Comentário enviado por paulloal em 26/03/2008 - 11:31h

tudo cupa da IEEE hUIAIuhA..
mto bom artigo..

[21] Comentário enviado por anonimo1234 em 29/03/2008 - 23:44h

Interessante,um bom exemplo da importancia da definição de tipos,pois o tipo apropriado para essa operação creio que seja float e não int,para não ficarmos sujeitos a arrendodamentos imperfeitos do C.

[22] Comentário enviado por SamL em 04/12/2008 - 23:21h

Eu executei o script e ocorreu o mesmo erro, 'a=339'
Mas consegui evitar isso fazendo assim:

#include <stdio.h>
int main()
{
int a;
a = 200;
a = (a *7)/10 + a;//ou a = (a*7/10) + a; ao inves de '(a*0.7)'
printf("O valor de a eh %d\n", a);
return 0;
}

e obtive o resultado correto:
'O valor de a eh 340'

[23] Comentário enviado por genilsondasilva em 09/10/2012 - 09:05h

O jeito é evitar misturar int com float. Agora se realmente for necessária essa conversão, devemos usar uma função para arredondamento.

Se o C tiver a função "round()" (ainda não testei), senão podemos criar uma função, por exemplo:

int arredonda(double num)
{
int num_int = num;
if( (num - num_int) >= 0.5)
{
return (num_int +1)
}
else
{
return (num_int)
}
}

Ou então:

#include <math.h>
#define ARRED(x) floor( x + 0.5 ) //Isso retorna um inteiro
#define ARRED2(x, z) floor( x*pow(10, z) + 0.5 )/pow(10, z) //sendo z o numero de casas decimais

Assim que for possível vou testar em casa no linux.


Contribuir com comentário