Avviso C ++: divisione del doppio per zero


98

Caso 1:

#include <iostream>

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

Si compila senza avvisi e stampa inf. OK, C ++ può gestire la divisione per zero, ( guardalo dal vivo ).

Ma,

Caso 2:

#include <iostream>

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

Il compilatore dà il seguente avviso ( guardalo dal vivo ):

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

Perché il compilatore dà un avviso nel secondo caso?

È 0 != 0.0?

Modificare:

#include <iostream>

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

produzione:

Same

9
Presumo che prenda zero come numero intero nel secondo caso e rilascia un avviso, anche se il calcolo sarebbe stato eseguito utilizzando double in seguito (che penso dovrebbe essere il comportamento quando d è un double).
Qubit

10
È un problema di QoI, davvero. Né l'avviso né la mancanza di un avviso è qualcosa imposto dallo standard C ++ stesso. Stai usando GCC?
StoryTeller - Unslander Monica


5
@StoryTeller Cos'è QoI? en.wikipedia.org/wiki/QoI ?
user202729

5
Per quanto riguarda la tua ultima domanda, "0 è uguale a 0,0?" La risposta è che i valori sono gli stessi, ma come hai scoperto, ciò non significa che siano identici. Tipi diversi! Proprio come "A" non è identico a 65.
Mr Lister

Risposte:


108

La divisione in virgola mobile per zero è ben definita da IEEE e fornisce l'infinito (positivo o negativo in base al valore del numeratore (o NaNper ± 0) ).

Per i numeri interi, non c'è modo di rappresentare l'infinito e il linguaggio definisce l'operazione in modo che abbia un comportamento indefinito, quindi il compilatore cerca utilmente di allontanarti da quel percorso.

Tuttavia in questo caso, poiché il numeratore è a double, anche il divisore ( 0) dovrebbe essere promosso a doppio e non c'è motivo di dare un avviso qui senza dare un avviso per 0.0quindi penso che questo sia un bug del compilatore.


8
Tuttavia, entrambe sono divisioni in virgola mobile. In d/0, 0viene convertito nel tipo di d.

43
Nota che C ++ non è richiesto per usare IEEE 754 (anche se non ho mai visto un compilatore che utilizza uno standard diverso) ..
Yksisarvinen

1
@hvd, buon punto, se in questo caso sembra un bug del compilatore
Motti

14
Sono d'accordo sul fatto che dovrebbe o avvisare in entrambi i casi, o non avvertire in entrambi i casi (a seconda di quale sia la gestione della divisione mobile per zero da parte del compilatore)
MM

8
Abbastanza sicuro che anche la divisione in virgola mobile per zero sia UB - è solo che GCC lo implementa secondo IEEE 754. Non devono farlo però.
Martin Bonner supporta Monica

42

In Standard C ++, entrambi i casi sono un comportamento non definito . Può succedere di tutto, inclusa la formattazione del disco rigido. Non dovresti aspettarti o fare affidamento su "return inf. Ok" o qualsiasi altro comportamento.

Apparentemente il compilatore decide di dare un avvertimento in un caso e non nell'altro, ma questo non significa che un codice sia OK e l'altro no. È solo una stranezza della generazione di avvisi del compilatore.

Dallo standard C ++ 17 [expr.mul] / 4:

L' /operatore binario restituisce il quoziente e l' %operatore binario restituisce il resto dalla divisione della prima espressione per la seconda. Se il secondo operando di /o %è zero il comportamento non è definito.


21
Non è vero, in aritmetica in virgola mobile la divisione per zero è ben definita.
Motti

9
@Motti - Se ci si limita al solo standard C ++, non ci sono tali garanzie. Lo scopo di questa domanda non è ben specificato, ad essere sinceri.
StoryTeller - Unslander Monica

9
@StoryTeller Sono abbastanza sicuro (anche se non ho esaminato il documento standard per questo) che se lo std::numeric_limits<T>::is_iec559è true, la divisione per zero per Tnon è UB (e sulla maggior parte delle piattaforme è trueper doublee float, sebbene per essere portabile dovrai è necessario verificarlo esplicitamente con un ifo if constexpr).
Daniel H

6
@DanielH - "Reasonable" è in realtà abbastanza soggettivo. Se questa domanda fosse etichettata come avvocato della lingua, ci sarebbe tutta un'altra serie (molto più piccola) di ipotesi ragionevoli.
StoryTeller - Unslander Monica

5
@MM Ricorda che è esattamente quello che hai detto, ma niente di più: undefined non significa che non siano imposti requisiti, significa che non sono imposti requisiti dallo standard . Direi che in questo caso, il fatto che un'implementazione definisce is_iec559come truesignifica che l' implementazione sta documentando un comportamento che lo standard lascia indefinito. È solo che questo è un caso in cui la documentazione dell'implementazione può essere letta a livello di programmazione. Nemmeno l'unico: lo stesso vale is_moduloper i tipi interi con segno.

12

La mia ipotesi migliore per rispondere a questa particolare domanda sarebbe che il compilatore emetta un avviso prima di eseguire la conversione di intin double.

Quindi, i passaggi sarebbero come questo:

  1. Parse espressione
  2. Operatore aritmetico /(T, T2) , dove T=double, T2=int.
  3. Verificare che std::is_integral<T2>::valuesia truee b == 0- questo attiva un avviso.
  4. Emetti un avviso
  5. Eseguire la conversione implicita di T2indouble
  6. Eseguire una divisione ben definita (poiché il compilatore ha deciso di utilizzare IEEE 754).

Questa è ovviamente una speculazione e si basa su specifiche definite dal compilatore. Dal punto di vista standard, abbiamo a che fare con possibili comportamenti indefiniti.


Si noti che questo è un comportamento previsto secondo la documentazione di GCC
(a proposito, sembra che questo flag non possa essere utilizzato esplicitamente in GCC 8.1)

-Wdiv-by-zero
Avvisa della divisione intera per zero in fase di compilazione. Questa è l'impostazione predefinita. Per inibire i messaggi di avviso, utilizzare -Wno-div-by-zero. La divisione in virgola mobile per zero non è avvertita, in quanto può essere un modo legittimo per ottenere infiniti e NaN.


2
Non è così che funzionano i compilatori C ++. Il compilatore deve eseguire la risoluzione dell'overload /per sapere che è una divisione. Se il lato sinistro fosse stato un Foooggetto e ci sarebbe stato un operator/(Foo, int), allora potrebbe anche non essere una divisione. Il compilatore conosce la sua divisione solo quando ha scelto di built-in / (double, double)utilizzare una conversione implicita del lato destro. Ma questo significa che NON sta facendo una divisione per int(0), sta facendo una divisione per double(0).
MSalters

@MSalters Si prega di vedere questo. La mia conoscenza del C ++ è limitata, ma in base al riferimento operator /(double, int)è sicuramente accettabile. Quindi, dice che la conversione viene eseguita prima di qualsiasi altra azione, ma GCC potrebbe spremere un rapido controllo se T2è di tipo intero b == 0ed emettere un avviso in tal caso. Non sono sicuro che sia completamente conforme agli standard, ma i compilatori hanno piena libertà nella definizione degli avvisi e quando devono essere attivati.
Yksisarvinen

2
Stiamo parlando dell'operatore integrato qui. È divertente. In realtà non è una funzione, quindi non puoi prenderne l'indirizzo. Quindi non puoi determinare se operator/(double,int)esiste davvero. Il compilatore può, ad esempio, decidere di ottimizzare a/bper costante bsostituendolo con a * (1/b). Ovviamente, ciò significa che non stai più chiamando operator/(double,double)in runtime ma più velocemente operator*(double,double). Ma ora è l'ottimizzatore che inciampa 1/0, la costante a cui dovrebbe fornireoperator*
MSalters

@MSalters Generalmente la divisione in virgola mobile non può essere sostituita con la moltiplicazione, probabilmente a parte casi eccezionali, come 2.
user202729

2
@ user202729: GCC lo fa anche per la divisione intera . Lascia che affondi per un momento. GCC sostituisce la divisione intera con la moltiplicazione intera. Sì, è possibile, perché GCC sa che sta operando su un anello (numeri modulo 2 ^ N)
MSalters

9

Non entrerò nella debacle di UB / non UB in questa risposta.

Voglio solo sottolineare questo 0e 0.0 sono diversi nonostante 0 == 0.0valutino vero. 0è un intletterale ed 0.0è un doubleletterale.

Tuttavia in questo caso il risultato finale è lo stesso: d/0è una divisione in virgola mobile perché dè double e quindi 0viene convertito implicitamente in double.


5
Non vedo come questo sia rilevante, dato che le solite conversioni aritmetiche specificano che la divisione di a doublecon un intmezzo in cui intviene convertito double , ed è specificato nello standard che 0converte in 0.0 (conv.fpint / 2)
MM

@MM l'OP vuole sapere se 0è lo stesso di0.0
bolov

2
La domanda dice "È 0 != 0.0?". OP non chiede mai se sono "uguali". Inoltre mi sembra che l'intento della domanda abbia a che fare con se d/0può comportarsi in modo diverso dad/0.0
MM

2
@MM - L'OP ha chiesto . Non stanno davvero mostrando una netiquette SO decente con quelle modifiche alle costanti.
StoryTeller - Unslander Monica

7

Direi che foo/0e nonfoo/0.0 sono la stessa cosa. Vale a dire, l'effetto risultante della prima (divisione intera o divisione in virgola mobile) dipende fortemente dal tipo di , mentre lo stesso non è vero per la seconda (sarà sempre una divisione in virgola mobile).foo

Se qualcuno dei due è UB è irrilevante. Citando lo standard:

Il comportamento indefinito ammissibile va dall'ignorare completamente la situazione con risultati imprevedibili, al comportamento durante la traduzione o l'esecuzione del programma in modo documentato caratteristico dell'ambiente (con o senza l'emissione di un messaggio diagnostico) , al termine di una traduzione o esecuzione (con l'emissione di un messaggio diagnostico).

(Enfasi mia)

Considera l' avvertimento " suggerisci parentesi attorno all'assegnazione usata come valore di verità ": il modo per dire al compilatore che vuoi veramente usare il risultato di un compito è essere espliciti e aggiungere parentesi attorno all'assegnazione. L'istruzione risultante ha lo stesso effetto, ma dice al compilatore che sai cosa stai facendo. Lo stesso si può dire su foo/0.0: poiché stai dicendo esplicitamente al compilatore "Questa è una divisione in virgola mobile" usando 0.0invece di 0, il compilatore si fida di te e non emetterà un avviso.


1
Entrambi devono essere sottoposti alle consuete conversioni aritmetiche per ottenere un tipo comune, che lascerà entrambi i casi divisione in virgola mobile.
Shafik Yaghmour

@ShafikYaghmour Hai perso il punto nella risposta. Nota che non ho mai menzionato qual è il tipo di file foo. Questo è intenzionale. La tua affermazione è vera solo nel caso in cui foosia un tipo a virgola mobile.
Cássio Renan

Non l'ho fatto, il compilatore ha informazioni sul tipo e comprende le conversioni, forse un analizzatore statico basato su testo potrebbe essere catturato da queste cose ma il compilatore non dovrebbe.
Shafik Yaghmour

Il punto è che sì, il compilatore conosce le solite conversioni aritmetiche, ma sceglie di non emettere un avviso quando il programmatore è esplicito. Il punto è che questo probabilmente non è un bug, ma invece è un comportamento intenzionale.
Cássio Renan

Quindi la documentazione a cui ho fatto riferimento non è corretta, poiché entrambi i casi sono divisioni in virgola mobile. Quindi o la documentazione è sbagliata o la diagnostica ha un bug.
Shafik Yaghmour

4

Sembra un bug di gcc, la documentazione di -Wno-div-by-zero dice chiaramente :

Non avvisare della divisione di interi in fase di compilazione per zero. La divisione in virgola mobile per zero non è avvertita, in quanto può essere un modo legittimo per ottenere infiniti e NaN.

e dopo le consuete conversioni aritmetiche descritte in [expr.arith.conv] entrambi gli operandi saranno doppi :

Molti operatori binari che prevedono operandi di tipo aritmetico o di enumerazione causano conversioni e producono tipi di risultati in modo simile. Lo scopo è produrre un tipo comune, che è anche il tipo del risultato. Questo modello è chiamato le solite conversioni aritmetiche, che sono definite come segue:

...

- Altrimenti, se uno degli operandi è double, l'altro deve essere convertito in double.

e [expr.mul] :

Gli operandi di * e / devono avere un tipo di enumerazione aritmetica o senza ambito; gli operandi di% devono avere un tipo di enumerazione integrale o senza ambito. Le consuete conversioni aritmetiche vengono eseguite sugli operandi e determinano il tipo di risultato.

Riguardo al fatto che la divisione in virgola mobile per zero sia un comportamento indefinito e il modo in cui la diversa implementazione lo gestisce sembra la mia risposta qui . TL; DR; Sembra che gcc sia conforme all'Allegato F rispetto alla divisione in virgola mobile per zero, quindi undefined non gioca un ruolo qui. La risposta sarebbe diversa per clang.


2

La divisione in virgola mobile per zero si comporta in modo diverso dalla divisione intera per zero.

Lo standard in virgola mobile IEEE distingue tra + inf e -inf, mentre gli interi non possono memorizzare l'infinito. Il risultato della divisione intera per zero è un comportamento indefinito. La divisione in virgola mobile per zero è definita dallo standard in virgola mobile e risulta in + inf o -inf.


2
Questo è vero ma non è chiaro in che modo ciò sia rilevante per la domanda, poiché la divisione in virgola mobile viene eseguita in entrambi i casi . Non esiste una divisione intera nel codice di OP.
Konrad Rudolph
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.