Se ho la seguente dichiarazione:
float a = 3.0 ;
è un errore? Ho letto in un libro che 3.0
è un double
valore e che devo specificarlo come float a = 3.0f
. È così?
Se ho la seguente dichiarazione:
float a = 3.0 ;
è un errore? Ho letto in un libro che 3.0
è un double
valore e che devo specificarlo come float a = 3.0f
. È così?
;
dopo.
Risposte:
Non è un errore da dichiarare float a = 3.0
: se lo fai, il compilatore convertirà il doppio letterale 3.0 in un float per te.
Tuttavia, dovresti usare la notazione dei letterali float in scenari specifici.
Per motivi di prestazioni:
In particolare, considera:
float foo(float x) { return x * 0.42; }
Qui il compilatore emetterà una conversione (che pagherai in fase di esecuzione) per ogni valore restituito. Per evitarlo dovresti dichiarare:
float foo(float x) { return x * 0.42f; } // OK, no conversion required
Per evitare bug durante il confronto dei risultati:
es. il seguente confronto fallisce:
float x = 4.2;
if (x == 4.2)
std::cout << "oops"; // Not executed!
Possiamo risolverlo con la notazione letterale float:
if (x == 4.2f)
std::cout << "ok !"; // Executed!
(Nota: ovviamente, non è così che dovresti confrontare i numeri in virgola mobile o doppi per l'uguaglianza in generale )
Per chiamare la funzione di sovraccarico corretta (per lo stesso motivo):
Esempio:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
int main()
{
foo(42.0); // calls double overload
foo(42.0f); // calls float overload
return 0;
}
Come notato da Cyber , in un contesto di deduzione del tipo, è necessario aiutare il compilatore a dedurre float
:
In caso di auto
:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
Allo stesso modo, in caso di detrazione del tipo di modello:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
template<typename T>
void bar(T t)
{
foo(t);
}
int main()
{
bar(42.0); // Deduce double
bar(42.0f); // Deduce float
return 0;
}
42
c'è un numero intero, che viene automaticamente promosso a float
(e accadrà in fase di compilazione in qualsiasi compilatore decente), quindi non c'è penalità nelle prestazioni. Probabilmente intendevi qualcosa di simile 42.0
.
4.2
in 4.2f
può avere l'effetto collaterale di impostare il FE_INEXACT
flag, a seconda del compilatore e del sistema, e alcuni (certamente pochi) programmi si preoccupano di quali operazioni a virgola mobile sono esatte e quali no, e testano quel flag . Ciò significa che la semplice e ovvia trasformazione in fase di compilazione altera il comportamento del programma.
float foo(float x) { return x*42.0; }
può essere compilato con una moltiplicazione a precisione singola, ed è stato compilato così da Clang l'ultima volta che ho provato. Tuttavia float foo(float x) { return x*0.1; }
non può essere compilato su una singola moltiplicazione a precisione singola. Potrebbe essere stato un po 'troppo ottimistico prima di questa patch, ma dopo la patch dovrebbe solo combinare conversion-double_precision_op-conversion a single_precision_op quando il risultato è sempre lo stesso. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
someFloat
, l'espressione someFloat * 0.1
darà risultati più accurati rispetto a someFloat * 0.1f
, mentre in molti casi sarà più economica di una divisione in virgola mobile. Ad esempio, (float) (167772208.0f * 0.1) verrà arrotondato correttamente a 16777220 anziché a 16777222. Alcuni compilatori possono sostituire una double
moltiplicazione per una divisione in virgola mobile, ma per quelli che non lo fanno (è sicuro per molti anche se non tutti i valori ) la moltiplicazione può essere un'ottimizzazione utile, ma solo se eseguita con un double
reciproco.
Il compilatore trasformerà uno qualsiasi dei seguenti valori letterali in float, perché hai dichiarato la variabile come float.
float a = 3; // converted to float
float b = 3.0; // converted to float
float c = 3.0f; // float
Importerebbe se usassi auto
(o altri metodi di detrazione del tipo), ad esempio:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
auto
non è l'unico caso.
Galleggianti letterali punto senza suffisso sono di tipo doppio , questo è trattato nel progetto C ++ parte standard 2.14.4
Floating letterali :
[...] Il tipo di un letterale floating è double a meno che non sia specificato esplicitamente da un suffisso. [...]
quindi è un errore assegnare 3.0
un doppio letterale a un float ?:
float a = 3.0
No, non lo è, verrà convertito, come descritto nella sezione 4.8
Conversioni in virgola mobile :
Un prvalue di tipo a virgola mobile può essere convertito in un prvalue di un altro tipo di virgola mobile. Se il valore di origine può essere rappresentato esattamente nel tipo di destinazione, il risultato della conversione è quella rappresentazione esatta. Se il valore di origine è compreso tra due valori di destinazione adiacenti, il risultato della conversione è una scelta definita dall'implementazione di uno di questi valori. In caso contrario, il comportamento non è definito.
Possiamo leggere maggiori dettagli sulle implicazioni di questo in GotW # 67: double or nothing che dice:
Ciò significa che una doppia costante può essere convertita implicitamente (cioè silenziosamente) in una costante float, anche se così facendo si perde precisione (cioè dati). Questo è stato consentito per motivi di compatibilità e usabilità C, ma vale la pena tenerlo a mente quando si lavora in virgola mobile.
Un compilatore di qualità ti avviserà se provi a fare qualcosa che ha un comportamento indefinito, vale a dire mettere una quantità doppia in un float che è inferiore al valore minimo, o maggiore del massimo, che un float è in grado di rappresentare. Un buon compilatore fornirà un avviso opzionale se provi a fare qualcosa che può essere definito ma potrebbe perdere informazioni, ovvero mettere una quantità doppia in un float che è tra i valori minimo e massimo rappresentabili da un float, ma che non può essere rappresentato esattamente come un galleggiante.
Quindi ci sono avvertenze per il caso generale di cui dovresti essere a conoscenza.
Da un punto di vista pratico, in questo caso i risultati saranno molto probabilmente gli stessi anche se tecnicamente c'è una conversione, possiamo vederlo provando il seguente codice su godbolt :
#include <iostream>
float func1()
{
return 3.0; // a double literal
}
float func2()
{
return 3.0f ; // a float literal
}
int main()
{
std::cout << func1() << ":" << func2() << std::endl ;
return 0;
}
e vediamo che i risultati per func1
e func2
sono identici, utilizzando sia clang
e gcc
:
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
Come fa notare Pascal in questo commento non potrai sempre contare su questo. L'uso di 0.1
e 0.1f
rispettivamente fa sì che l'assembly generato differisca poiché la conversione deve ora essere eseguita in modo esplicito. Il codice seguente:
float func1(float x )
{
return x*0.1; // a double literal
}
float func2(float x)
{
return x*0.1f ; // a float literal
}
risulta nel seguente assemblaggio:
func1(float):
cvtss2sd %xmm0, %xmm0 # x, D.31147
mulsd .LC0(%rip), %xmm0 #, D.31147
cvtsd2ss %xmm0, %xmm0 # D.31147, D.31148
ret
func2(float):
mulss .LC2(%rip), %xmm0 #, D.31155
ret
Indipendentemente dal fatto che tu possa determinare se la conversione avrà o meno un impatto sulle prestazioni, l'utilizzo del tipo corretto documenta meglio la tua intenzione. Ad esempio, utilizzando conversioni esplicitestatic_cast
aiuta anche a chiarire che la conversione era intesa e non accidentale, il che potrebbe significare un bug o un potenziale bug.
Nota
Come sottolinea supercat, la moltiplicazione per es 0.1
e 0.1f
non è equivalente. Citerò solo il commento perché è stato eccellente e un riassunto probabilmente non gli renderebbe giustizia:
Ad esempio, se f fosse uguale a 100000224 (che è esattamente rappresentabile come float), moltiplicandolo per un decimo dovrebbe produrre un risultato che arrotonda per difetto a 10000022, ma moltiplicando per 0,1f produrrà invece un risultato che arrotonda erroneamente fino a 10000023 Se l'intenzione è di dividere per dieci, la moltiplicazione per doppia costante 0,1 sarà probabilmente più veloce della divisione per 10f e più precisa della moltiplicazione per 0,1f.
Il mio punto originale era quello di dimostrare un falso esempio fornito in un'altra domanda, ma questo dimostra con precisione che possono esistere problemi sottili negli esempi di giocattoli.
f = f * 0.1;
e f = f * 0.1f;
fanno cose diverse . Ad esempio, se f
fosse uguale a 100000224 (che è esattamente rappresentabile come a float
), moltiplicandolo per un decimo dovrebbe produrre un risultato che arrotonda per difetto a 10000022, ma moltiplicando per 0,1f produrrà invece un risultato che arrotonda erroneamente a 10000023. Se l'intenzione è di dividere per dieci, la moltiplicazione per la double
costante 0.1 sarà probabilmente più veloce della divisione per 10f
e più precisa della moltiplicazione per 0.1f
.
Non è un errore nel senso che il compilatore lo rifiuterà, ma è un errore nel senso che potrebbe non essere quello che vuoi.
Come afferma correttamente il tuo libro, 3.0
è un valore di tipo double
. C'è una conversione implicita da double
a float
, quindi float a = 3.0;
è una definizione valida di una variabile.
Tuttavia, almeno concettualmente, questo esegue una conversione inutile. A seconda del compilatore, la conversione può essere eseguita in fase di compilazione o può essere salvata per il runtime. Un motivo valido per salvarlo per il tempo di esecuzione è che le conversioni in virgola mobile sono difficili e possono avere effetti collaterali imprevisti se il valore non può essere rappresentato esattamente e non è sempre facile verificare se il valore può essere rappresentato esattamente.
3.0f
evita questo problema: sebbene tecnicamente, il compilatore è ancora autorizzato a calcolare la costante in fase di esecuzione (lo è sempre), qui, non c'è assolutamente alcun motivo per cui un compilatore potrebbe farlo.
Sebbene non sia un errore, di per sé, è un po 'sciatto. Sai che vuoi un float, quindi inizializzalo con un float.
Un altro programmatore potrebbe arrivare e non essere sicuro di quale parte della dichiarazione sia corretta, il tipo o l'inizializzatore. Perché non fare in modo che entrambi siano corretti?
risposta a virgola mobile = 42.0f;
Quando si definisce una variabile, viene inizializzata con l'inizializzatore fornito. Ciò potrebbe richiedere la conversione del valore dell'inizializzatore nel tipo della variabile che viene inizializzata. Questo è ciò che accade quando dici float a = 3.0;
: il valore dell'inizializzatore viene convertito in float
e il risultato della conversione diventa il valore iniziale di a
.
In genere va bene, ma non fa male scrivere 3.0f
per dimostrare che sei consapevole di quello che stai facendo, soprattutto se vuoi scrivere auto a = 3.0f
.
Se provi quanto segue:
std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;
otterrai l'output come:
4:8
ciò mostra, la dimensione di 3.2f viene presa come 4 byte su una macchina a 32 bit, mentre 3.2 viene interpretata come un valore doppio che prende 8 byte su una macchina a 32 bit. Questo dovrebbe fornire la risposta che stai cercando.
double
e float
sono diversi, non risponde se è possibile inizializzare un float
da un doppio letterale
Il compilatore deduce il tipo più adatto dai letterali, o almeno ciò che ritiene sia il più adatto. Questo è piuttosto perdere efficienza rispetto alla precisione, cioè utilizzare un double invece di float. In caso di dubbio, utilizzare le parentesi graffe per renderlo esplicito:
auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int
La storia diventa più interessante se si inizializza da un'altra variabile in cui si applicano le regole di conversione del tipo: sebbene sia legale costruire una forma doppia in letterale, non può essere costruita da un int senza possibile restringimento:
auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'
3.0
in un float per te. Il risultato finale è indistinguibile dafloat a = 3.0f
.