Por que dividir dois int não produz o valor correto quando atribuído a double?


110

Como é que no seguinte snippet

int a = 7;
int b = 3;
double c = 0;
c = a / b;

cacaba tendo o valor 2, em vez de 2,3333, como seria de esperar. Se ae bforem duplos, a resposta muda para 2.333. Mas certamente porque c já é um duplo deveria ter funcionado com inteiros?

Então, por int/int=doubleque não funciona?


Possível duplicata do resultado
phuclv

Respostas:


161

Isso ocorre porque você está usando a versão de divisão inteira de operator/, que leva 2 se intretorna um int. Para usar a doubleversão, que retorna a double, pelo menos um dos ints deve ser convertido explicitamente em a double.

c = a/(double)b;

9
Eu preferiria converter explicitamente tanto ae bpara doublesimplesmente para maior clareza, mas realmente não importa.
John Dibling

31
Uma vez que a pergunta está marcada como C ++, eu preferiria ver static_cast <> em vez de um elenco C.
Martin York

16
Pessoalmente, eu sinto que os moldes do estilo C são mais claros (a conversão na maioria das outras linguagens comuns é feita no estilo C). static_cast<>sempre pareceu prolixo para mim. No caso de primitivos, não há realmente nenhum perigo de confusão static_cast<>e reinterpret_cast<>confusão.
Chad La Guardia

6
@ Tux-D: Para elencos aritméticos? Eu preferiria evitar static_castneste caso e usar elenco de estilo C. Não há nenhum benefício em usar casts de estilo C ++ aqui e eles bagunçam o código muito mais do que casts de estilo C. O elenco aritmético é exatamente o contexto em que os elencos no estilo C são perfeitamente apropriados e, na verdade, mais apropriados do que outros elencos.
AnT

19
Às vezes você pode enganar o pessoal "sem elenco de estilo C" escrevendo double(b). Eles nem sempre percebem que é uma conversão, uma vez que se parece com uma chamada explícita do construtor.
Steve Jessop

12

Aqui está:

a) Dividir dois ints executa sempre a divisão inteira. Portanto, o resultado de a/bno seu caso só pode ser um int.

Se você deseja manter ae bcomo ints, ainda dividi-los totalmente, você deve lançar pelo menos um deles para dobrar: (double)a/bou a/(double)bou (double)a/(double)b.

b) cé a double, para que possa aceitar um intvalor na atribuição: o inté automaticamente convertido para doublee atribuído a c.

c) Lembre-se de que na atribuição, a expressão à direita de =é calculada primeiro (de acordo com a regra (a) acima, e sem levar em conta a variável à esquerda de =) e, em seguida, atribuída à variável à esquerda de =(de acordo com ( b) acima). Eu acredito que isso completa o quadro.


11

Com muito poucas exceções (só consigo pensar em uma), C ++ determina todo o significado de uma expressão (ou subexpressão) a partir da própria expressão. O que você faz com os resultados da expressão não importa. No seu caso, na expressão a / b, não há um doubleà vista; tudo é int. Portanto, o compilador usa divisão inteira. Somente depois de obter o resultado, ele considera o que fazer com ele e o converte em double.


3
A única exceção em que posso pensar é escolher uma sobrecarga de função ao obter um ponteiro - o valor de &funcnamedepende do tipo para o qual você o converte.
Steve Jessop

2
@Steve Jessop Essa é a única exceção que eu consigo pensar também. (Mas dado o tamanho e a complexidade do padrão, eu não gostaria de jurar que não perdi nenhum.)
James Kanze

6

cé uma doublevariável, mas o valor que está sendo atribuído a ela é um intvalor porque resulta da divisão de dois ints, o que dá a você "divisão inteira" (descartando o restante). Então, o que acontece na linha c=a/bé

  1. a/b é avaliado, criando um temporário do tipo int
  2. o valor do temporário é atribuído capós a conversão em tipo double.

O valor de a/bé determinado sem referência ao seu contexto (atribuição a double).


6

Ao dividir dois números inteiros, o resultado será um número inteiro, independentemente do fato de você armazená-lo em um duplo.


5

Na linguagem C ++, o resultado da subexpressão nunca é afetado pelo contexto circundante (com algumas raras exceções). Este é um dos princípios que a linguagem segue cuidadosamente. A expressão c = a / bcontém uma subexpressão independente a / b, que é interpretada independentemente de qualquer coisa fora dessa subexpressão. O idioma não se importa se mais tarde você atribuirá o resultado a a double. a / bé uma divisão inteira. Qualquer outra coisa não importa. Você verá este princípio seguido em muitos aspectos da especificação da linguagem. É assim que C ++ (e C) funciona.

Um exemplo de exceção que mencionei acima é a atribuição / inicialização do ponteiro de função em situações com sobrecarga de função

void foo(int);
void foo(double);

void (*p)(double) = &foo; // automatically selects `foo(fouble)`

Este é um contexto em que o lado esquerdo de uma atribuição / inicialização afeta o comportamento do lado direito. (Além disso, a inicialização de referência a array evita o decaimento do tipo array, que é outro exemplo de comportamento semelhante.) Em todos os outros casos, o lado direito ignora completamente o lado esquerdo.


4

O /operador pode ser usado para divisão inteira ou divisão de ponto flutuante. Você está dando a ele dois operandos inteiros, então ele está fazendo a divisão de inteiros e o resultado está sendo armazenado em um duplo.


2

Isso é tecnicamente dependente do idioma, mas quase todos os idiomas tratam esse assunto da mesma forma. Quando há uma incompatibilidade de tipo entre dois tipos de dados em uma expressão, a maioria das linguagens tentará converter os dados em um lado do= para corresponder aos dados do outro lado de acordo com um conjunto de regras predefinidas.

Ao dividir dois números do mesmo tipo (inteiros, duplos, etc.), o resultado será sempre do mesmo tipo (então 'int / int' sempre resultará em int).

Neste caso, você tem o double var = integer result que converte o resultado inteiro em um dobro após o cálculo , caso em que os dados fracionários já estão perdidos. (a maioria dos idiomas fará essa conversão para evitar imprecisões de tipo sem gerar uma exceção ou erro).

Se você gostaria de manter o resultado como um dobro, você vai querer criar uma situação em que tenha double var = double result

A maneira mais fácil de fazer isso é forçar a expressão do lado direito de uma equação para dobrar:

c = a/(double)b

A divisão entre um inteiro e um duplo resultará na conversão do inteiro para o duplo (observe que, ao fazer matemática, o compilador freqüentemente fará um "upcast" para o tipo de dados mais específico para evitar a perda de dados).

Após o upcast, aterminará como um duplo e agora você tem divisão entre dois duplos. Isso criará a divisão e atribuição desejadas.

NOVAMENTE, observe que isso é específico da linguagem (e pode até ser específico do compilador), no entanto, quase todas as linguagens (certamente todas as que eu consigo pensar de início) tratam esse exemplo de forma idêntica.


Esta questão é marcada como [C ++], e o padrão C ++ dita exatamente como isso funciona. Não tenho certeza do que você entende por "específico da linguagem" e certamente não é específico do compilador, assumindo que nenhuma extensão do compilador esteja ativada.
John Dibling

Também é incorreto dizer que "double var = integer result que converte double var para int". O duplo não é lançado em um int. O resultado int é convertido em duplo.
John Dibling

Eu estava permitindo a possibilidade de extensões do compilador (na verdade, tive esse problema uma vez em que meu ambiente estava "lançando mal" os resultados e não consegui descobrir o porquê). E o resultado é específico do idioma, já que alguns idiomas não seguem as mesmas regras de fundição. Não considerei que fosse uma tag específica do C ++. Você está certo sobre o comentário "double var = integer result". Editado para refletir isso. Obrigado!
matthewdunnam

0

O importante é que um dos elementos do cálculo seja do tipo float-double. Então, para obter um resultado duplo, você precisa lançar este elemento como mostrado abaixo:

c = static_cast<double>(a) / b;

ou c = a / static_cast (b);

Ou você pode criá-lo diretamente ::

c = 7.0 / 3;

Observe que um dos elementos de cálculo deve ter '.0' para indicar uma divisão de um tipo float-double por um inteiro. Caso contrário, apesar da variável c ser dupla, o resultado também será zero.


O que sua resposta traz que nenhuma das outras 9 respostas ainda não está presente?
bolov
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.