Por que a divisão de inteiros resulta em um inteiro?

Nós aprendemos na Introdução à programação que se você dividir dois inteiros, sempre obterá um inteiro. Para corrigir o problema, torne pelo menos um desses inteiros um número flutuante.

Por que o compilador não entende que eu quero que o resultado seja um número decimal?

Comentários

  • Talvez você queira que o resultado seja um número inteiro. Como isso pode dizer a diferença?
  • Porque o padrão C ++ diz isso.
  • @soandos: A maneira como Pascal e Python fazem isso: têm um operador de divisão de inteiro distinto, mas o operador de divisão padrão sempre retorna o resultado matematicamente correto. (Ou – para o pedante – o mais correto possível, dado o limitações da matemática FP.)
  • Como o compilador saberia que você deseja que o resultado seja um número decimal? Existem razões válidas para querer um inteiro.
  • @soandos Em Python, // é o operador de divisão inteira e # é um comentário de uma única linha. Em Pascal, o operador de divisão inteira é a palavra-chave div. Ambos funcionam muito bem para seus respectivos idiomas ges. C provavelmente faz a pior coisa possível: um operador que pode fazer duas coisas completamente diferentes com base em um contexto arbitrário.

Resposta

Por que o compilador não entende que eu quero que o resultado seja um número decimal?

O compilador C ++ está simplesmente seguindo regras bem definidas e determinísticas conforme estabelecido no padrão C ++. O padrão C ++ tem essas regras porque o comitê de padrões decidiu torná-lo assim.

Eles poderiam ter escrito o padrão para dizer que matemática inteira resulta em números de ponto flutuante, ou faz isso apenas em o caso de um resto. No entanto, isso adiciona complexidade: ou eu preciso saber com antecedência qual é o resultado, ou talvez converter de volta para um número inteiro se sempre der um float. Talvez eu queira um número inteiro .

Uma das filosofias centrais do C ++ é “você não paga pelo que não usa . ” Se você realmente deseja a complexidade de misturar inteiros e flutuantes (e as instruções extras da CPU e acessos à memória, isso acarreta 1 ), faça o tipo de conversão conforme mencionou em sua pergunta . Caso contrário, atenha-se à matemática de inteiros padrão.

Finalmente, misturar variáveis integrais e de ponto flutuante pode resultar em perda de precisão e, às vezes, em resultados incorretos, como discuto abaixo. Se você quiser isso, pague por isso: caso contrário, o padrão determina que os compiladores sigam um conjunto estrito de regras para misturar tipos de dados. Este é um comportamento bem definido: como um desenvolvedor C ++, posso pesquisar isso no padrão e ver como funciona.


Existem essencialmente três maneiras de fazer o que você está tentando fazer, cada um com vantagens e desvantagens.

  • Matemática inteira: isso resulta em resultados truncados durante a divisão, como você descobriu. Se quiser a parte decimal, você precisa tratá-la separadamente, dividindo, obtendo o resto e tratando a parte decimal como o resto dividido pelo divisor. Esta é uma operação um pouco mais complexa e tem mais variáveis para manipular.

  • Matemática de ponto flutuante: geralmente produzirá resultados corretos (o suficiente) para valores pequenos, mas pode introduza erros facilmente com precisão e arredondamento, especialmente à medida que o expoente aumenta. Se você dividir um número grande por um número pequeno, pode até causar um estouro negativo ou simplesmente obter um resultado errado, porque as escalas dos números não combinam bem entre si.

  • Faça sua própria matemática. Existem classes por aí que manipulam precisão estendida de números decimais e racionais . Normalmente, eles são mais lentos do que a matemática nos tipos integrados, mas geralmente ainda são muito rápidos e fornecem matemática de precisão arbitrária. Arredondamentos e outros problemas não são automáticos como com flutuadores IEEE, mas você ganha mais controle e certamente mais precisão.

A chave aqui é escolher com base no domínio do problema . Todos os três métodos de representação de números têm suas próprias vantagens e desvantagens. Usando um contador de loop? Escolha um tipo integral. Representando locais no espaço 3D? Provavelmente um grupo de carros alegóricos. Quer rastrear dinheiro? Use um tipo decimal fixo.


1 Arquiteturas de CPU mais populares (por exemplo, x86-64 ) terá conjuntos separados de instruções que operam em tipos de registro diferentes, como inteiro e ponto flutuante, além de instruções extras para converter entre integral, ponto flutuante e várias representações deles (com e sem sinal, flutuante e duplo). Algumas dessas operações também podem envolver acesso à memória: converter um valor e armazená-lo na memória (sua variável). A matemática no nível da CPU não é tão simples quanto “entrada inteira, saída flutuante.”Embora adicionar dois inteiros possa ser uma operação muito simples, possivelmente uma única instrução, misturar tipos de dados pode aumentar a complexidade.

Comentários

  • Você diz que o padrão C ++ estipula que esse comportamento deve ser assim. Por quê? Não ‘ isso tornaria as coisas mais fáceis de dizer, ” Divisão de números inteiros que não são uniformemente divisíveis resulta em flutuantes, qualquer outra divisão é válida. ”
  • @ moonman239 veja minhas edições.
  • @ moonman239 Não para escritores de compiladores. Muitas arquiteturas de CPU comumente usadas fornecem um resultado inteiro quando solicitadas a fazer a divisão com dois inteiros. Eles teriam que implementar uma verificação de resultados não inteiros e, em seguida, alternar para usar o ponto flutuante mais lento Como alternativa, eles poderiam ter padronizado a divisão de ponto flutuante e perdido o interesse daqueles que queriam matemática rápida, aqueles que queriam matemática precisa e aqueles que estavam acostumados com C. Mudando agora não é ‘ uma opção, porque isso quebraria a compatibilidade com o código existente.
  • Não que você esteja defendendo isso como uma alternativa, mas tornando o tipo estático de uma expressão dependem dos valores de tempo de execução dos operandos que ‘ não funcionam com o sistema de tipo estático C ++ ‘ s.
  • @ moonman239: Ter uma operação que produz um tipo diferente dependendo dos valores dos operandos é uma loucura total.

Resposta

Isso se deve à evolução do hardware. Nos primeiros dias dos computadores, nem todas as máquinas tinham uma unidade de ponto flutuante, o hardware simplesmente não era capaz de entender a noção de um número de ponto flutuante. Claro, os números de ponto flutuante podem ser implementados como uma abstração de software, mas isso tem desvantagens significativas. Toda a aritmética nessas máquinas tinha que ser aritmética de inteiro puro por padrão.

E ainda hoje, há uma distinção firme entre unidades aritméticas de inteiros e de ponto flutuante dentro de uma CPU. Seus operandos são armazenados em arquivos de registro separados para começar, e uma unidade de número inteiro é conectada para receber dois argumentos de número inteiro e produzir um resultado de número inteiro que termina em um registro de número inteiro. Algumas CPUs até exigem que um valor inteiro seja armazenado na memória e, em seguida, recarregado de volta em um registrador de ponto flutuante, antes que possa ser recodificado em um número de ponto flutuante, antes que você possa realizar uma divisão de ponto flutuante nele.

Como tal, a decisão feita pelos desenvolvedores C no início da linguagem (C ++ simplesmente herdou esse comportamento), foi a única decisão apropriada a fazer, e continua valendo hoje: Se você precisa de matemática de ponto flutuante, você pode usá-lo. Se você não precisa, bem, você não precisa.

Comentários

  • É triste que a maioria das restrições que existiam no a criação do padrão C ++ está bastante obsoleta hoje! Por exemplo: ” você não paga pelo que não usa. ” hoje em dia, o hardware é um dado adquirido e tudo que os usuários desejam é execução!
  • @ mahen23 Nem todos os usuários pensam assim. Eu trabalho em uma área onde os programas são executados em milhares de núcleos de CPU em paralelo. Nesta área, eficiência é dinheiro, tanto em termos de investimentos quanto em termos de consumo absoluto de energia. Uma linguagem como Java não tem a menor chance nessa área, enquanto C ++ sim.
  • @ mahen23 Não, não é ‘ t – ou melhor, é só é se você olhar para as arquiteturas de CPU atuais para desktops e superiores. Ainda existem muitos sistemas embarcados que não ‘ t ou apenas parcialmente suportam operações de ponto flutuante, e C, bem como C ++ continuam a suportá-los a fim de fornecer a implementação mais eficiente possível antes de usando assembler. A propósito, linguagens de nível ainda mais alto, como Python, distinguem entre operações de inteiro e FP – tente 10 / 3.

Resposta

10/2 com inteiros fornece exatamente 5 – a resposta correta.

Com matemática de ponto flutuante, 10/2 pode fornecer o correto resposta *.

Em outras palavras, é impossível que os números de ponto flutuante sejam “perfeitos” no hardware atual – apenas a matemática inteira pode ser correta, infelizmente não pode “fazer casas decimais, mas é um trabalho fácil ao redor.

Por exemplo, em vez de 4/3, faça (4 * 1000) / (3 * 1000) == 1333. Basta desenhar um no software ao exibir a resposta para o seu usuário (1.333). Isso lhe dá uma resposta precisa, em vez de uma que esteja incorreta por um certo número de casas decimais.

Erros matemáticos de vírgula flutuante podem causar erros significativos – qualquer coisa importante (como finanças) usará matemática inteira .

* o exemplo 10/2 estará realmente correto com a matemática de ponto flutuante, mas você não pode contar com isso, muitos outros números dão resultados incorretos …para mais detalhes, leia: http://http.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps A questão é que você não pode “confiar na precisão sempre que os pontos flutuantes estiverem envolvidos

Comentários

  • Implementações de ponto flutuante em conformidade com IEEE 754 fornecerão um resultado exato para 2/10. Na verdade, eles fornecerão resultados para qualquer operação envolvendo apenas operandos inteiros que têm um resultado inteiro, desde que os operandos e o resultado possam ser representados exatamente, o que números inteiros “pequenos o suficiente” podem.
  • @ 5gon12eder there ‘ s não há necessidade de escolher nit, eu ‘ estou apenas tentando descrever um problema complexo em termos simples. O objetivo de apoiar valores não inteiros é ter casas decimais ( que pode ser feito usando números inteiros simplesmente multiplicando tudo pelo número de casas decimais que você deseja, conforme eu demsonstraed).

Resposta

Embora tecnicamente não seja totalmente correto, C ++ ainda é considerado um superconjunto de C, foi inspirado por ele e, como tal, se apropriou de algumas de suas propriedades, sendo a divisão inteira uma delas.

C foi projetado principalmente para ser eficiente e rápido, e os inteiros geralmente são muito mais rápido do que os pontos flutuantes, porque o tipo inteiro está vinculado ao hardware, enquanto os pontos flutuantes precisam ser calculados.

Quando o operando / recebe dois inteiros, um do lado esquerdo e um à direita, pode nem mesmo fazer divisão, o resultado pode ser calculado usando adição simples e um loop, perguntando quantas vezes o operando do lado direito se encaixa no operando da esquerda.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *