Gli analizzatori di codice sorgente (come SonarQube) si lamentano dei confronti di uguaglianza float (o doppia) perché l'uguaglianza è una cosa difficile con i float; i valori confrontati possono essere i risultati di calcoli che spesso hanno effetti di arrotondamento minuti, in modo che 0.3 - 0.2 == 0.1
spesso restituisca false mentre matematicamente dovrebbe sempre restituire true (come testato con Python 2.7). Quindi questa lamentela ha perfettamente senso avvertire del codice potenzialmente pericoloso.
Un approccio tipico per tali situazioni è il controllo di un margine, un epsilon, che dovrebbe compensare tutti gli effetti di arrotondamento, e. g.
if abs(a - b) < epsilon then …
D'altra parte si può spesso vedere un codice che evita un problema di divisione per zero controllando il divisore per l'uguaglianza con lo zero prima che la divisione abbia luogo:
if divisor == 0.0 then
// do some special handling like skipping the list element,
// return 0.0 or whatever seems appropriate, depending on context
else
result = divident / divisor
endif
Questo sembra gestire il problema div-per-zero ma non è compatibile con l'analizzatore del codice sorgente che si lamenta ancora dello spot divisor == 0.0
. A prima vista sembra un problema con l'analizzatore. Sembra un falso positivo. I controlli di uguaglianza di flusso per 0.0 dovrebbero essere consentiti, non dovrebbero?
Dopo alcune considerazioni ho pensato al caso in cui il divisore era il risultato di un calcolo che avrebbe dovuto avere come risultato 0.0 (come 0.3 - 0.2 - 0.1
) e che ora era qualcosa nell'intervallo di 1e-17
o 0.00000000000000001
.
Ci sono due approcci per questo ora:
- Il valore non è esattamente 0.0, quindi la divisione può aver luogo, il valore risultante sarà un numero "normale" in virgola mobile (probabilmente, considerare 1e200 / 1e-200 che è
inf
). Lascia che succeda, il chiamante deve prendersi cura dei risultati.
o
- Il valore avrebbe dovuto essere 0.0, logicamente è in questo caso, il computer semplicemente non se ne accorge, quindi qualunque trattamento speciale del caso zero era inteso dovrebbe avvenire anche qui.
Se votiamo per la seconda opzione, potremmo usare l'approccio epsilon e andarci bene. Ma questo tratterà i veri valori diversi da zero che sono solo molto piccoli come i valori zero-ish. Non abbiamo modo di distinguere i due casi.
Questo porta alla successiva considerazione se un vero valore diverso da zero che sia molto vicino a 0.0 debba comunque essere diviso o se dovrebbe essere gestito come il caso zero (cioè ricevere lo speciale movimentazione). Dopotutto, la divisione con un valore così piccolo si tradurrà in valori di grandi dimensioni che saranno spesso problematici (in grafici o simili). Questo è sicuramente all'altezza del contesto e non si può rispondere in generale.
Ho anche considerato se l'esistenza di valori zero (-ish) nell'input non fosse forse la radice del problema ma solo un effetto in sé, i. e. forse la radice del problema si trova più in profondità: forse un algoritmo che si aspetta un float e che dovrebbe dividere per esso non dovrebbe mai ricevere valori che possono diventare zero (-ish) in primo luogo.
Posso pensare ai casi d'uso con numeri interi in cui è necessario controllare che siano zero prima di dividerli (ad esempio un indice la cui differenza rispetto a un indice di riferimento è usata come divisore, quando entrambi diventano uguali in qualche iterazione, la differenza è 0
), ma non ho potuto pensare ad un buon esempio in cui un valore float potrebbe diventare zero-ish. Forse se si fosse verificata una cosa del genere, era solo un errore logico?
Quindi, ora le mie domande sono:
- Esiste una teoria sull'argomento dei controlli a virgola mobile per evitare problemi di divisione per zero che riguardano le mie considerazioni? Non ho ancora trovato nulla su Internet.
- Qualcuno può fornire un ragionevole esempio di un contesto e un algoritmo al suo interno che dovrebbe aspettarsi valori fluttuanti che possono diventare zero e con i quali dovrebbe dividere? E a seconda di quale contesto quale soluzione (epsilon, pure
== 0.0
-check, forse un approccio diverso) preferiresti lì?