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!
O C é uma linguagem compilada que aceita que o programador tenha certas liberdades. Ele dificilmente critica (com erro de compilação) alguma atribuição que se faça, mesmo que envolva tipos de dados completamente diferentes. Permite que inteiros sejam atribuídos para char, doubles para inteiros e assim por diante.
Sempre que uma mesma operação matemática envolver tipos de dados diferentes, o C realizará uma conversão implícita de dados, ou seja, um ou mais dados serão convertidos para outro tipo durante a operação. Como exemplo disto:
int x;
x = 34.234;
No exemplo acima o valor 34.234 não pode ser atribuído ao x, pois x é inteiro e não aceita números com ponto flutuante. O número de ponto flutuante 34.234, neste caso, é convertido para int antes da atribuição. A conversão, neste caso, se dá simplesmente eliminando as casas decimais sendo que x receberá apenas 34.
Agora um exemplo mais interessante:
int x;
char c;
x = 1000;
c = x;
Neste caso tem-se que o tamanho de uma variável char é de um byte (8 bits) e da variável x de 32 bits (considerando um compilador de 32 bits). Em x cabe tranquilamente o valor 1000, mas em c, por ser meros 8 bits, o espaço de valores que cabe é o seguinte:
de 0 até 255 se a variável não estiver considerando sinal
de -128 a 127 se estiver considerando sinal.
Logo, atribuir 1000 para c resulta que não tem espaço para armazenar. Alguns compiladores, como o Pascal, param a compilação neste momento e indicam erro porque o programador misturou tipos de dados em uma mesma operação. O C, claro, compila sem problemas.
Mas qual é o valor que c receberá?
Para isto será necessário decompor o valor 1000 em 32 bits:
O byte menos significativo de 1000 é 11101000 que se lido separadamente, apenas ele, resulta no decimal 232. Como c tem a possibilidade de apenas um byte, é apenas este byte que é atribuído ao c.
Logo, qual o valor de c? 232?
Sim e não!
Se a variável c NÃO estiver usando sinal, sim, o valor é 232. Porém se estiver usando sinal (e o Intel usa complemento de 2 para representar números negativos) o valor inteiro interpretado por c (ao imprimir ele com um %d por exemplo) será -24:
O compilador C faz o tempo todo conversões implícitas, seja inserindo bytes em 0 (para conversão de char para int SEM sinal), seja inserindo bytes em 1 (para conversão de char para int COM SINAL) ou cortando bytes:
char a;
int x;
a = -2; /* em binário 11111110 */
x = a; /* x deve ter -2, que em binário 32 bits é
11111111 11111111 11111111 11111110
Neste caso o C inseriu três bytes em UM para fazer com que o x tivesse -2.
Se ele inserisse três bytes em ZERO x teria 254 e não -2.
Ele, o C, faria isto se o a fosse declarado como unsigned:
unsigned char a;
*/
[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.
[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)
[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.
[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?
[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...
[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
[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'
#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.