2011-10-05 14 views
24

In C++ (o forse solo i nostri compilatori VC8 e VC10)3.14 è un doppio letterale e 3.14f è un valore letterale float.Dovremmo generalmente usare i letterali float per i float invece dei doppi letterali più semplici?

ora ho un collega che ha dichiarato:

Dovremmo usare float-letterali per i calcoli float e double-letterali per i doppi calcoli come questo potrebbe avere un impatto sulla precisione di un calcolo quando vengono utilizzate le costanti in un calcolo.

In particolare, credo che volesse dire:

double d1, d2; 
float f1, f2; 
... init and stuff ... 
f1 = 3.1415 * f2; 
f1 = 3.1415f * f2; // any difference? 
d1 = 3.1415 * d2; 
d1 = 3.1415f * d2; // any difference? 

Oppure, aggiunto da me, anche:

d1 = 42 * d2; 
d1 = 42.0f * d2; // any difference? 
d1 = 42.0 * d2; // any difference? 

Più in generale, il punto unica posso vedere per l'utilizzo 2.71828183f è per assicurarmi che la costante che sto cercando di specificare si inserisca effettivamente in un float (errore/avviso del compilatore in caso contrario).

Qualcuno può far luce su questo? Specifica il suffisso f? Perché?

citare una risposta quello che implicitamente dato per scontato:

Se si lavora con una variabile float e un doppio letterale tutta operazione sarà fatto come doppio e poi convertito torna a galleggiare .

Potrebbe esserci qualche danno in questo? (Altro che un impatto molto, molto teorico prestazioni?)

Ulteriori edit: Sarebbe bello se le risposte che contengono dettagli tecnici potrebbero includere anche come queste differenze influenzano codice general purpose (apprezzato!). (Sì, se stai facendo il numero, probabilmente ti piace assicurarti che le tue operazioni in virgola mobile più grandi siano efficienti (e corrette) il più possibile - ma è importante per il codice di uso generale che viene chiamato un paio di volte? t pulito se il codice usa solo 0.0 e salta il suffisso - difficile da mantenere! - float?)

+1

Un esempio è qui: http://ideone.com/r8K2c –

risposta

43

Sì, è necessario utilizzare il suffisso f. Motivi includono:

  1. Prestazioni. Quando scrivi float foo(float x) { return x*3.14; }, imponi al compilatore di emettere il codice che converte x in double, quindi esegue la moltiplicazione, quindi converte il risultato in singolo. Se aggiungi il suffisso f, entrambe le conversioni vengono eliminate. Su molte piattaforme, ciascuna di queste conversioni è costosa quanto la moltiplicazione stessa.

  2. Prestazioni (continua). Esistono piattaforme (la maggior parte dei cellulari, ad esempio), su cui l'aritmetica a doppia precisione è notevolmente più lenta della precisione singola.Anche ignorando il sovraccarico di conversione (trattato in 1.), ogni volta che imponi un calcolo da valutare in doppio, rallenti il ​​programma. Questo non è solo un problema "teorico".

  3. Riduci la tua esposizione agli insetti. Considerare l'esempio float x = 1.2; if (x == 1.2) // something; È eseguito something? No, non lo è, perché x detiene 1.2 arrotondato a float, ma viene confrontato con il valore a precisione doppia 1.2. I due non sono uguali.

+3

+10 per il punto 3! –

+0

+1 - indirizzi alcuni punti pratici! –

+0

Stephen - Si noti che una delle ragioni sottostanti che sto chiedendo è che * mantenere * il suffisso float è "impossibile" (AFAIK) perché il nostro compilatore non avvisa. Quindi, potremmo applicare questa regola, ma non potrebbe essere applicata e l'uso sarebbe solo aneddotico e incoerente. (O almeno così temo). Nota: dubito che ci siano dei percorsi critici di codice in cui un * letterale * è effettivamente utilizzato in un calcolo qui. –

1

C'è una differenza: se si utilizza una doppia costante e la si moltiplica con una variabile float, la variabile è convertito in doppio prima, il calcolo viene eseguito in doppio, quindi il risultato viene convertito in float. Sebbene la precisione non sia davvero un problema qui, questo potrebbe portare a confusione.

+0

* In che modo * questo potrebbe causare confusione? (Di chi?) –

+1

Mi dispiace di avere solo esempi esagerati nella mia testa, ma questo potrebbe portare a specializzazioni inaspettate o scelta di sovraccarico, ad esempio, confondendo lo sviluppatore che non è a conoscenza della promozione. – thiton

9

Sospetto qualcosa del genere: se si sta lavorando con una variabile float e una doppia letterale, l'intera operazione verrà eseguita come doppia e quindi riconvertita in float.

Se si utilizza un valore letterale float, a livello teorico il calcolo verrà eseguito con precisione flottante anche se alcuni hardware lo convertono in doppio in ogni caso per eseguire il calcolo.

+3

+1 per * anche se alcuni componenti hardware lo convertono in doppio * –

+0

o altro. x86 utilizza la precisione estesa a 80 bit per i registri a virgola mobile. – Puppy

+1

@DeadMG: non per le istruzioni SIMD?! – smerlin

1

Personalmente tendo ad usare la notazione f postfix come una questione di principi e per rendere il più ovvio possibile che questo sia un tipo float piuttosto che un double.

miei due centesimi

1

Dal C++ Standard (Working Draft), sezione 5 da operatori binari

molti operatori binari che prevedono operandi di operazioni aritmetiche o tipo censimento causa conversioni e tipi di risultato resa in un modo simile. Lo scopo è quello di fornire un tipo comune, che è anche il tipo di il risultato. Questo modello è chiamato le solite conversioni aritmetiche, che sono definite come segue: - Se uno degli operandi è del tipo di enumerazione con scope (7.2), non vengono eseguite conversioni; se l'altro operando non ha lo stesso tipo, l'espressione è mal formata. - Se uno degli operandi è di tipo long double, l'altro deve essere convertito a in double double. - Altrimenti, se uno degli operandi è doppio, l'altro deve essere convertito in doppio. - Altrimenti, se uno degli operandi è mobile, l'altro deve essere convertito in float.

E anche paragrafo 4,8

Un prvalue di tipo floating point può essere convertito in un prvalue di altro tipo floating point. Se il valore di origine può essere esattamente rappresentato 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 è unde fi nita

Il risultato di questo è che si può evitare conversioni inutili specificando costanti nella precisione dettata dal tipo di destinazione, a condizione che non si perde precisione nel calcolo così facendo (cioè, i tuoi operandi sono esattamente rappresentabili nella precisione del tipo di destinazione).

+0

Sì, ma queste * conversioni inutili * faranno del male? (In realtà mi chiedo se saranno visibili anche nell'assemblaggio.) –

+0

@Martin Se sono visibili nell'assieme, causano danni e sono solo il costo delle prestazioni della conversione.Naturalmente potremmo sempre presumere che i nostri compilatori facciano ogni ottimizzazione per noi, dovremmo aiutarci il più possibile e tenere a mente la differenza tra precisione singola e doppia è cruciale per scrivere codice numericamente stabile. Se ti interessa la differenza tra precisione singola e doppia, usa i letterali corretti. Se non ti interessa, puoi sempre usare il doppio ovunque. –

+0

@Christian: Mi sto davvero chiedendo se si tratterebbe solo di micro-ottimizzazione non necessaria. –

3

In genere, non penso che farà alcuna differenza, ma vale la pena indicando che 3.1415f e 3.1415 sono (in genere) non uguali. Su d'altra parte, normalmente non si fanno calcoli in float , almeno sulle solite piattaforme. (double è altrettanto veloce, se non è più veloce.) L'unica volta che si dovrebbe vedere float è quando ci sono grandi matrici, e anche allora, tutti i calcoli saranno tipicamente in double.

+1

Ci sono molte piattaforme su cui 'float' è più veloce di' double', inclusa una delle piattaforme di elaborazione più diffuse di tutti - un tipico smartphone. –

+0

James - intendi dire che '3.1415f' convertito in double è * not * uguale a un valore double inizializzato dal doppio letterale' 3.1415' (e viceversa)? Perché dovrebbe essere? –

+6

@Martin Sì. Il motivo per cui non sono uguali è perché hanno valori diversi. '3.1415' non può essere esattamente rappresentato in' float' né 'double' (almeno non su una delle solite piattaforme). '3.1415' viene convertito nel valore più vicino rappresentabile in un' double', e '3.1415f' viene convertito nel valore più vicino rappresentabile in un' float'. Assegnare il '3.1415' a un' float' probabilmente finirà per arrotondare alla stessa cosa di '3.1415f', ma assegnare' 3.1415f' a un 'double' produrrà comunque il valore esattamente rappresentabile in un' float'. –

5

Ho fatto un test.

ho compilato questo codice:

float f1(float x) { return x*3.14; }    
float f2(float x) { return x*3.14F; } 

Utilizzando 4.5.1 gcc per i686 con l'ottimizzazione -O2.

Questo è stato il codice assembly generato per f1:

pushl %ebp 
movl %esp, %ebp 
subl $4, %esp # Allocate 4 bytes on the stack 
fldl .LC0  # Load a double-precision floating point constant 
fmuls 8(%ebp) # Multiply by parameter 
fstps -4(%ebp) # Store single-precision result on the stack 
flds -4(%ebp) # Load single-precision result from the stack 
leave 
ret 

e questo è il codice assembly generato per f2:

pushl %ebp 
flds .LC2   # Load a single-precision floating point constant 
movl %esp, %ebp 
fmuls 8(%ebp)  # Multiply by parameter 
popl %ebp 
ret 

Quindi la cosa interessante è che per la F1, il compilatore memorizzato il valore e ricaricato solo per assicurarsi che il risultato è stato troncato a precisione singola.

Se usiamo l'opzione -ffast-matematica, allora questa differenza si riduce notevolmente:

pushl %ebp 
fldl .LC0    # Load double-precision constant 
movl %esp, %ebp 
fmuls 8(%ebp)   # multiply by parameter 
popl %ebp 
ret 


pushl %ebp 
flds .LC2    # Load single-precision constant 
movl %esp, %ebp 
fmuls 8(%ebp)   # multiply by parameter 
popl %ebp 
ret 

Ma c'è ancora la differenza tra il caricamento di una singola o doppia precisione costante.

Aggiornamento per 64-bit

Questi sono i risultati con gcc 5.2.1 per x86-64 con l'ottimizzazione -O2:

f1:

cvtss2sd %xmm0, %xmm0  # Convert arg to double precision 
mulsd  .LC0(%rip), %xmm0 # Double-precision multiply 
cvtsd2ss %xmm0, %xmm0  # Convert to single-precision 
ret 

f2:

mulss  .LC2(%rip), %xmm0 # Single-precision multiply 
ret 

Con -ffast-math, i risultati sono gli stessi.

+0

Entra nell'era moderna; (1) compilare per x86_64 e (2) attivare il codegen SSE2. –

+0

Belle informazioni! Ma è veramente importante per il codice generale? –

+1

@ Martin: se le prestazioni non sono un problema, suppongo di no. –

Problemi correlati