Aviso C ++: divisão de duplo por zero

98

Caso 1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}

Compila sem avisos e imprime inf. OK, C ++ pode lidar com divisão por zero, ( veja ao vivo ).

Mas,

Caso 2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}

O compilador dá o seguinte aviso ( veja ao vivo ):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;

Por que o compilador dá um aviso no segundo caso?

É 0 != 0.0?

Editar:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}

resultado:

Same
Jayesh
fonte
9
Suponho que leva zero como um inteiro no segundo caso e descarta um aviso, mesmo se o cálculo fosse feito usando double posteriormente (o que eu acho que deveria ser o comportamento quando d é um double).
Qubit
10
É um problema de QoI, na verdade. Nem o aviso ou a falta de um aviso é algo exigido pelo próprio padrão C ++. Você está usando o GCC?
StoryTeller - Unslander Monica
5
@StoryTeller O que é QoI? en.wikipedia.org/wiki/QoI ?
user202729
5
Com relação à sua última pergunta, "0 é igual a 0,0?" A resposta é que os valores são os mesmos, mas, como você descobriu, isso não significa que eles sejam idênticos. Tipos diferentes! Assim como 'A' não é idêntico a 65.
Sr. Lister

Respostas:

108

A divisão de ponto flutuante por zero é bem definida pelo IEEE e dá infinito (positivo ou negativo de acordo com o valor do numerador (ou NaNpara ± 0) ).

Para inteiros, não há como representar o infinito e a linguagem define a operação como tendo um comportamento indefinido, portanto, o compilador tenta ajudar você a desviá-lo desse caminho.

No entanto, neste caso, uma vez que o numerador é um double, o divisor ( 0) deve ser promovido a um duplo também e não há razão para dar um aviso aqui, embora não seja um aviso para, 0.0então acho que este é um bug do compilador.

Motti
fonte
8
Porém, ambas são divisões de ponto flutuante. Em d/0, 0é convertido para o tipo de d.
43
Observe que C ++ não é necessário para usar IEEE 754 (embora eu nunca tenha visto um compilador usando um padrão diferente) ..
Yksisarvinen
1
@hvd, bom ponto, nesse caso, parece um bug do compilador
Motti
14
Eu concordaria que deveria avisar em ambos os casos, ou não avisar em ambos os casos (dependendo de como o compilador lidou com a divisão flutuante por zero)
MM
8
Tenho certeza de que a divisão de ponto flutuante por zero também é UB - é só que o GCC a implementa de acordo com o IEEE 754. Eles não precisam fazer isso.
Martin Bonner apoia Monica em
42

No C ++ padrão, ambos os casos são comportamento indefinido . Tudo pode acontecer, incluindo a formatação do seu disco rígido. Você não deve esperar ou confiar em "return inf. Ok" ou qualquer outro comportamento.

O compilador aparentemente decide dar um aviso em um caso e não no outro, mas isso não significa que um código está OK e o outro não. É apenas uma peculiaridade da geração de avisos do compilador.

Do padrão C ++ 17 [expr.mul] / 4:

O /operador binário produz o quociente, e o %operador binário produz o resto da divisão da primeira expressão pela segunda. Se o segundo operando de /ou %for zero, o comportamento é indefinido.

MILÍMETROS
fonte
21
Não é verdade, na aritmética de ponto flutuante a divisão por zero é bem definida.
Motti
9
@Motti - Se alguém se limitar apenas ao padrão C ++, não haverá tais garantias. O escopo dessa questão não está bem especificado, para ser franco.
StoryTeller - Unslander Monica
9
@StoryTeller Tenho quase certeza (embora não tenha olhado o próprio documento padrão para isso) que se std::numeric_limits<T>::is_iec559for true, então a divisão por zero para Tnão é UB (e na maioria das plataformas é truepara doublee float, embora seja portátil, você precisa verificar isso explicitamente com um ifou if constexpr).
Daniel H
6
@DanielH - "Razoável" é na verdade bastante subjetivo. Se essa questão fosse marcada como advogado de linguagem , haveria todo um outro conjunto (muito menor) de suposições razoáveis.
StoryTeller - Unslander Monica
5
@MM Lembre-se de que é exatamente o que você disse, mas nada mais: indefinido não significa que nenhum requisito é imposto, significa que nenhum requisito é imposto pela norma . Eu diria que, neste caso, o fato de uma implementação definir is_iec559como truesignifica que a implementação está documentando o comportamento que o padrão deixa indefinido. É apenas que este é um caso em que a documentação da implementação pode ser lida programaticamente. Nem mesmo o único: o mesmo se aplica a is_modulotipos inteiros com sinal.
12

Meu melhor palpite para responder a essa pergunta em particular seria que o compilador emite um aviso antes de realizar a conversão de intpara double.

Então, as etapas seriam assim:

  1. Expressão de análise
  2. Operador aritmética /(T, T2) , onde T=double, T2=int.
  3. Verifique se std::is_integral<T2>::valueé truee b == 0- isso dispara um aviso.
  4. Aviso de emissão
  5. Execute a conversão implícita de T2paradouble
  6. Execute uma divisão bem definida (já que o compilador decidiu usar IEEE 754).

É claro que isso é especulação e se baseia em especificações definidas pelo compilador. Do ponto de vista padrão, estamos lidando com possíveis comportamentos indefinidos.


Observe que este é o comportamento esperado de acordo com a documentação do GCC
(aliás, parece que este sinalizador não pode ser usado explicitamente no GCC 8.1)

-Wdiv-by-zero Avisa
sobre a divisão inteira por zero em tempo de compilação. Este é o padrão. Para inibir as mensagens de aviso, use -Wno-div-by-zero. A divisão de ponto flutuante por zero não é alertada, pois pode ser uma forma legítima de obter infinitos e NaNs.

Yksisarvinen
fonte
2
Não é assim que os compiladores C ++ funcionam. O compilador deve executar a resolução de sobrecarga /para saber que é divisão. Se o lado esquerdo fosse um Fooobjeto e houvesse um operator/(Foo, int), então poderia nem mesmo haver divisão. O compilador só sabe sua divisão quando escolheu built-in / (double, double)usar uma conversão implícita do lado direito. Mas isso significa que NÃO está fazendo uma divisão por int(0), está fazendo uma divisão por double(0).
MSalters
@MSalters Por favor, veja isso. Meu conhecimento sobre C ++ é limitado, mas de acordo com a referência operator /(double, int)certamente é aceitável. Em seguida, ele diz que a conversão é realizada antes de qualquer outra ação, mas o GCC poderia fazer uma verificação rápida se T2for do tipo inteiro b == 0e emitir um aviso em caso afirmativo. Não tenho certeza se isso é totalmente compatível com o padrão, mas os compiladores têm total liberdade para definir os avisos e quando eles devem ser disparados.
Yksisarvinen
2
Estamos falando sobre o operador embutido aqui. Isso é engraçado. Na verdade, não é uma função, então você não pode pegar seu endereço. Portanto, você não pode determinar se operator/(double,int)realmente existe. O compilador pode, por exemplo, decidir otimizar a/ba constante bsubstituindo-a por a * (1/b). Claro, isso significa que você não está mais chamando operator/(double,double)em tempo de execução, mas o mais rápido operator*(double,double). Mas agora é o otimizador que tropeça 1/0, a constante para a qual ele teria que alimentaroperator*
MSalters
@MSalters Geralmente, a divisão de ponto flutuante não pode ser substituída pela multiplicação, provavelmente exceto em casos excepcionais, como 2.
user202729
2
@ user202729: GCC faz isso até mesmo para divisão de inteiros . Deixe isso cair por um momento. O GCC substitui a divisão inteira pela multiplicação inteira. Sim, isso é possível, porque o GCC sabe que está operando em um anel (números módulo 2 ^ N)
MSalters
9

Não vou entrar no desastre UB / não UB nesta resposta.

Eu só quero apontar isso 0e 0.0 são diferentes apesar de 0 == 0.0avaliarem como verdade. 0é um intliteral e 0.0é um doubleliteral.

No entanto, neste caso, o resultado final é o mesmo: d/0é uma divisão em ponto flutuante porque dé dupla e, portanto, 0está implicitamente convertida em dupla.

Bolov
fonte
5
Não vejo como isso é relevante, dado que as conversões aritméticas usuais especificam que dividir a doublepor um intsignifica para o qual o inté convertido double e é especificado no padrão que 0converte para 0.0 (conv.fpint / 2)
MM
@MM, o OP quer saber se 0é o mesmo que0.0
bolov
2
A pergunta diz "É 0 != 0.0?". OP nunca pergunta se eles são "iguais". Também me parece que a intenção da pergunta é se d/0pode se comportar de maneira diferente ded/0.0
MM
2
@MM - O OP perguntou . Eles não estão realmente mostrando uma netiqueta de SO decente com essas edições constantes.
StoryTeller - Unslander Monica
7

Eu argumentaria isso foo/0e nãofoo/0.0 são os mesmos. Ou seja, o efeito resultante da primeira (divisão inteira ou divisão de ponto flutuante) é altamente dependente do tipo de , enquanto o mesmo não é verdade para a segunda (será sempre uma divisão de ponto flutuante).foo

Se algum dos dois é UB é irrelevante. Citando o padrão:

O comportamento indefinido permissível varia de ignorar a situação completamente com resultados imprevisíveis, para se comportar durante a tradução ou execução do programa de maneira documentada característica do ambiente (com ou sem a emissão de uma mensagem de diagnóstico) , para encerrar uma tradução ou execução (com a emissão de uma mensagem de diagnóstico).

(Ênfase minha)

Considere o aviso " sugira parênteses em torno da atribuição usada como valor verdade ": a maneira de dizer ao compilador que você realmente deseja usar o resultado de uma atribuição é sendo explícito e adicionando parênteses ao redor da atribuição. A instrução resultante tem o mesmo efeito, mas informa ao compilador que você sabe o que está fazendo. O mesmo pode ser dito sobre foo/0.0: Como você está explicitamente dizendo ao compilador "Esta é uma divisão de ponto flutuante" usando em 0.0vez de 0, o compilador confia em você e não emitirá um aviso.

Cássio Renan
fonte
1
Ambos têm que passar pelas conversões aritméticas usuais para chegar a um tipo comum, o que deixará ambos os casos com divisão em ponto flutuante.
Shafik Yaghmour
@ShafikYaghmour Você não entendeu o ponto da resposta. Observe que nunca mencionei qual é o tipo de foo. Isso é intencional. Sua afirmação só é verdadeira no caso de fooser um tipo de ponto flutuante.
Cássio Renan
Eu não, o compilador tem informações de tipo e entende as conversões, talvez um analisador estático baseado em texto possa ser pego por tais coisas, mas o compilador não.
Shafik Yaghmour
Meu ponto é que sim, o compilador conhece as conversões aritméticas usuais, mas opta por não emitir um aviso quando o programador está sendo explícito. A questão toda é que isso provavelmente não é um bug, mas sim um comportamento intencional.
Cássio Renan
Então, a documentação que apontei está incorreta, uma vez que ambos os casos são divisões de ponto flutuante. Portanto, ou a documentação está errada ou o diagnóstico tem um bug.
Shafik Yaghmour
4

Parece um bug do gcc, a documentação -Wno-div-by-zero diz claramente :

Não avise sobre a divisão inteira do tempo de compilação por zero. A divisão de ponto flutuante por zero não é alertada, pois pode ser uma forma legítima de obter infinitos e NaNs.

e após as conversões aritméticas usuais abordadas em [expr.arith.conv], ambos os operandos serão duplos :

Muitos operadores binários que esperam operandos do tipo aritmético ou enumeração causam conversões e produzem tipos de resultado de maneira semelhante. O objetivo é produzir um tipo comum, que também é o tipo do resultado. Esse padrão é chamado de conversões aritméticas usuais, que são definidas da seguinte maneira:

...

- Caso contrário, se um dos operandos for duplo, o outro deve ser convertido para duplo.

e [expr.mul] :

Os operandos de * e / devem ter tipo de enumeração aritmética ou sem escopo; os operandos de% devem ter tipo de enumeração integral ou sem escopo. As conversões aritméticas usuais são realizadas nos operandos e determinam o tipo de resultado.

Com relação a se a divisão de ponto flutuante por zero é um comportamento indefinido e como diferentes implementações lidam com isso, parece minha resposta aqui . TL; DR; Parece que o gcc está em conformidade com o Anexo F em relação ao ponto flutuante dividido por zero, portanto, undefined não desempenha um papel aqui. A resposta seria diferente para clang.

Shafik Yaghmour
fonte
2

A divisão de ponto flutuante por zero se comporta de maneira diferente da divisão de inteiro por zero.

O padrão de ponto flutuante IEEE diferencia entre + inf e -inf, enquanto os inteiros não podem armazenar o infinito. A divisão inteira por resultado zero é um comportamento indefinido. A divisão de ponto flutuante por zero é definida pelo padrão de ponto flutuante e resulta em + inf ou -inf.

Rizwan
fonte
2
Isso é verdade, mas não está claro como isso é relevante para a questão, uma vez que a divisão de ponto flutuante é realizada em ambos os casos . Não há divisão de inteiros no código do OP.
Konrad Rudolph