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: 125.150 ]

Por: Elgio Schlemer em 20/03/2008 | Blog: https://profelgio.duckdns.org/~elgio


Nem tudo é representável



Em nossa notação decimal, que aprendemos desde o início, como poderia ser representado o 1/3?

1/3 = 0,33333333333333...

Isto chama-se "dizima periódica". Observe que 1/3 é IMPOSSÍVEL de ser representado! A medida que eu incluo mais casas decimais, vou me aproximando do verdadeiro 1/3, mas nunca chego a representá-lo de forma 100% precisa.

Um número em ponto flutuante em C (double) usa oito bytes (64 bits) para representação. Um número FINITO de bits portanto. Este número FINITO de bits será usado para representar quantos números? Infinitos? Ora, não tem como. Ai alguém pode responder: não, deve ser capaz de representar apenas números de X até Y.

Ok, mas estamos falando de números reais, certo? Quantos outros números existem entre um simples 0,1 e 0,2? Ou entre um 0,00001 e 0,0002? Entre um número real e qualquer outro número real existem outros infinitos números reais. Não foi o C quem inventou isto!

Logo, é fácil imaginar que para alguns números reais a representação é IMPOSSÍVEL e o C fará o possível para chegar o mais perto possível. Chegar mais perto de um ou de outro jeito depende muito de quantos bits a variável usa. Neste caso são 64 bits.

Quer saber da maior?

double x;
x = 0.7;
printf ("%30.20lf\n", x);

Ao pedir ao C que imprima 0,7 com vinte casas decimais, somos surpreendidos com a impressão de 0.69999999999999995559.

Está aí! 0.7 não é realmente 0.7. É quase, muito perto. Em PHP 0.7 é representável, mas 0.71 não:

<?php
    printf ("0,7  = %30.20lf\n”, 0,7);
    printf ("0,71 = %30.20lf\n", 0.71);
?>

imprimirá:

0,7 = 0.70000000000000000000
0,71 = 0.70999999999999996447

Logo, isto é em qualquer linguagem, só que pode aparecer para uns ou outros números, dependendo do tamanho de um double e do tratamento que a linguagem faz. O PHP, por exemplo, realiza arredondamento. Já o C NÃO!

Página anterior     Próxima página

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

255.255.255.0: A matemática das máscaras de rede

Criptografia assimétrica com o RSA

Fundamentos da criptografia assimétrica

Parâmetros interessantes do scanf e do printf em C

Criptografia chave simétrica de bloco e de fluxo

Leitura recomendada

Algum humor e C++ Design Patterns (parte 1)

Mapear objetos em C

SDL e C - Uma dupla sensacional

Programação com números inteiros gigantes

Programação de Jogos com SDL

  
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




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts