5

Siamo sicuri di utilizzare i float come contatori di loop e di incrementarli/decrementarli di quantità frazionarie ad ogni iterazione, come nel seguente programma apparentemente privo di rischi? Naturalmente so che usare i float come operandi per lo == l'operatore è una cosa stupida da fare. Ma cosa c'è di sbagliato nell'usare i float come operandi per altre operazioni di confronto per scopi "normali"? Con "normale" intendo ciò, beh, anche se i float potrebbero non essere la esatta rappresentazione digitale del numero, ma non è una variazione come 0.000000001 irrilevante e può essere ignorata nella maggior parte dei casi? (Ad esempio, nel seguente programma che non è nemmeno apparente)Qualche rischio di utilizzare variabili float come contatori di loop e il loro incremento/decremento frazionario per condizioni non "=="?

Ma detto questo, ecco la mia apprehension.Suppose la rappresentazione non è esatta e 5,0 è in realtà 4,999,999 mila .Così come andiamo sul decremento da 0,5 a ogni iterazione, l'ultimo confronto con potrebbe risultare falso e il loop potrebbe uscire a causa di una differenza di 0,000001 e l'ultima riga dell'output corrente non verrà visualizzata. Spero che tu stia prendendo la mia strada. Come sbaglio, sono io?

#include<stdio.h> 

int main(void) 
{ 
float f; 

for(f=5.0;f>=0;f-=0.5) 
printf("%f\n",f); 
} 

uscita:

5.000000 
4.500000 
4.000000 
3.500000 
3.000000 
2.500000 
2.000000 
1.500000 
1.000000 
0.500000 
0.000000 

risposta

11

No, non è sicuro, per i motivi indicati nella domanda stessa. Considerate questo:

#include<stdio.h> 

int main(void) { 
    float f = 1.0; 

    for(;f>0;f-=0.1) 
    printf("%f\n",f); 
    return 0; 
} 

Questo esempio sembra funzionare quite ok quando f viene inizializzata dal 1.0. Ma cambiare questo a 3,0 - e le cose iniziano a farsi strada more interesting abbastanza presto:

2.600000 
2.500000 
2.400001 
... 
0.000001 

... che porta al fallimento famigerato 'off-by-one'.


si pensa che si potrebbe essere al sicuro con >= invece di >? Pensate again:

float f = 5.0; 
for(;f>=1;f-=0.4) 
    printf("%f\n",f); 

... 
3.400000 
3.000000 
2.599999 
2.199999 
1.799999 
1.399999 

... e off-by-one ci risiamo (come 0.99999 è inferiore a 1).

8

Finché il valore iniziale, l'importo decremento e il risultato di tutti i decrementi possono essere rappresentati con alcun errore entro la precisione ottenibile con il tipo a virgola mobile, allora è sicuro da usare. Nota che "nessun errore" qui significa 0 errore assoluto, un errore molto piccolo è ancora considerato un errore.

Nel tuo caso, il valore di partenza 5.0 e la quantità decremento 0.5 possono essere rappresentati senza errori, e 4.5, 4.0, 3.5, ..., 0.0 può essere rappresentato anche senza errori entro 23-bit di precisione di float . È sicuro nel tuo caso.

Se diciamo che il valore di partenza è 4000000.0 e la quantità decremento è 0.00390625 (2 -8), allora sei nei guai, perché il risultato del decremento non può essere rappresentato senza errori in 23-bit di precisione di float digita, anche se il valore iniziale e l'importo di decremento possono essere rappresentati correttamente.

Tuttavia, non vedo alcun punto nell'utilizzo di virgola mobile, quando il tipo integrale è più affidabile in tal caso. Non devi sprecare cellule cerebrali per verificare se la condizione che ho dichiarato sopra si applica o no.

+0

+1 Risposta piacevole. –

+0

Dovrebbe essere chiaro che questi criteri si applicano ai cicli che terminano a zero. Se si dispone di un ciclo che va da + X a -Y, ad esempio, allora possono verificarsi errori se Y è maggiore di X. –

+0

@EricPostpischil: Penso che la condizione che il valore risultante nel mezzo deve essere rappresentabile copre quel caso? – nhahtdh

6

Preferire valori interi su virgola mobile quando possibile semplicemente a causa dei problemi con la rappresentazione in virgola mobile.

Invece di utilizzare il numero in virgola mobile come il controllo ad anello, rielaborare la logica di utilizzare interi:

Necessità di decrementare il vostro contatore .5? Raddoppia il tuo valore iniziale e decremento dell'1:

float f = 5.0; 
int i = f * 2; 

for(; i >= 0; i--) 
    printf("%f\n", i/2.0); 

c'è da diminuire dal .1?

float f = 5.0; 
int i = f * 10; 

for(; i >= 0; i--) 
    printf("%f\n", i/10.0); 

Questo è un approccio semplice per l'esempio nella domanda. Certamente non è l'unico approccio o il più corretto. Un esempio più complesso potrebbe richiedere una rielaborazione della logica un po 'diversa. Qualunque cosa si adatta alla situazione.

Il mio punto suppongo sia di ritardare il lavoro con il valore attuale del punto fluttuante fino all'ultimo momento possibile per ridurre l'introduzione di errori dovuti alla rappresentazione.

+0

+1 per gli esempi che mostrano il modo migliore per eseguire il loop sui float. –

Problemi correlati