La versione inline di una funzione restituisce un valore diverso dalla versione non inline


85

Come possono due versioni della stessa funzione, che differiscono solo per il fatto che una è inline e l'altra no, restituire valori diversi? Ecco un po 'di codice che ho scritto oggi e non sono sicuro di come funzioni.

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

Mi aspetto che tutti gli output siano uguali 1, ma in realtà restituisce questo (g ++ 8.3.1, nessun flag):

1
0
1

invece di

1
1
1

Modifica: clang ++ 7.0.0 restituisce questo:

0
0
0

e g ++ -veloce questo:

1
1
1

3
Puoi per favore fornire quale compilatore, opzioni del compilatore stai usando e quale macchina? Funziona bene per me su GCC 7.1 su Windows.
Diodaco

31
Non è ==sempre un po 'imprevedibile con valori in virgola mobile?
500 - Errore interno del server


2
Hai impostato l' -Ofastopzione che consente tali ottimizzazioni?
cmdLP

4
Il compilatore restituisce cbrt(27.0)il valore di 0x0000000000000840while la libreria standard restituisce 0x0100000000000840. I doppi differiscono per il sedicesimo numero dopo la virgola. Il mio sistema: archlinux4.20 x64 gcc8.2.1 glibc2.28 Controllato con questo . Mi chiedo se gcc o glibc abbiano ragione.
KamilCuk

Risposte:


73

Spiegazione

Alcuni compilatori (in particolare GCC) usano una precisione maggiore quando valutano le espressioni in fase di compilazione. Se un'espressione dipende solo da input e letterali costanti, può essere valutata in fase di compilazione anche se l'espressione non è assegnata a una variabile constexpr. Se ciò si verifica o meno dipende da:

  • La complessità dell'espressione
  • La soglia utilizzata dal compilatore come interruzione quando tenta di eseguire la valutazione in fase di compilazione
  • Altre euristiche utilizzate in casi speciali (come quando clang elides si ripete)

Se un'espressione viene fornita esplicitamente, come nel primo caso, ha una complessità inferiore ed è probabile che il compilatore la valuti in fase di compilazione.

Allo stesso modo, se una funzione è contrassegnata come inline, è più probabile che il compilatore la valuti in fase di compilazione perché le funzioni inline aumentano la soglia alla quale può verificarsi la valutazione.

Livelli di ottimizzazione più alti aumentano anche questa soglia, come nell'esempio -Ofast, dove tutte le espressioni restituiscono true su gcc a causa di una valutazione in fase di compilazione più precisa.

Possiamo osservare questo comportamento qui su Esplora risorse del compilatore. Quando viene compilato con -O1, solo la funzione contrassegnata come inline viene valutata in fase di compilazione, ma in -O3 entrambe le funzioni vengono valutate in fase di compilazione.

NB: Negli esempi compilatore-esploratore, utilizzo printfinvece iostream perché riduce la complessità della funzione principale, rendendo più visibile l'effetto.

Dimostrare che inlinenon influisce sulla valutazione del runtime

Possiamo garantire che nessuna delle espressioni venga valutata in fase di compilazione ottenendo il valore dall'input standard e, quando lo facciamo, tutte e 3 le espressioni restituiscono false come dimostrato qui: https://ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

Contrasto con questo esempio , in cui usiamo le stesse impostazioni del compilatore ma forniamo il valore in fase di compilazione, con conseguente valutazione in fase di compilazione più precisa.


22

Come osservato, l'utilizzo ==dell'operatore per confrontare i valori in virgola mobile ha prodotto output diversi con compilatori diversi ea diversi livelli di ottimizzazione.

Un buon modo per confrontare i valori in virgola mobile è il test di tolleranza relativa delineato nell'articolo: Tolleranze in virgola mobile rivisitate .

Calcoliamo prima il valore Epsilon(la tolleranza relativa ) che in questo caso sarebbe:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

E poi usalo in entrambe le funzioni inline e non inline in questo modo:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

Le funzioni ora sono:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

Ora l'output sarà come previsto ( [1 1 1]) con diversi compilatori e diversi livelli di ottimizzazione.

Dimostrazione dal vivo


Qual è lo scopo della max()chiamata? Per definizione, floor(x)è minore o uguale a x, quindi max(x, floor(x))sarà sempre uguale x.
Ken Thomases

@KenThomases: in questo caso particolare, dove un argomento maxè solo floorl'altro, non è obbligatorio. Ma ho considerato un caso generale in cui gli argomenti per maxpossono essere valori o espressioni indipendenti l'uno dall'altro.
PW

Non dovresti operator==(double, double)fare esattamente questo, verificare che la differenza sia inferiore a un epsilon in scala? Allora circa il 90% delle domande relative a virgola mobile su SO non esisterebbe.
Peter - Ripristina Monica il

Penso che sia meglio se l'utente può specificare il Epsilonvalore in base alle proprie esigenze particolari.
PW
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.