Evitare la divisione di zero usando il confronto di galleggiamento

5

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:

  1. 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

  1. 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:

  1. 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.
  2. 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ì?
posta Alfe 05.02.2016 - 16:39
fonte

4 risposte

4

Sono schierato dalla teoria personale di OP che non è una pratica normale consentire a un programma per computer di procedere con un'operazione di divisione per zero, o di eseguire solo un controllo minimo prima della divisione.

L'eccezione è quando stai implementando qualcosa che è troppo generale - un linguaggio di programmazione (come MATLAB) in cui tu (come programmatore) non conosci il contesto / applicazione / use-case / significato fisico delle operazioni matematiche è chiesto di esibirsi. Ciò può essere dovuto al fatto che la formula che sta valutando è fornita dal cliente e non si conosce il caso d'uso del cliente di tale formula. In tal caso si utilizza una rappresentazione speciale come Inf o NaN come segnaposto.

Se, tuttavia, la formula viene fornita come parte di una cassetta degli attrezzi statistica, dovresti essere in grado di fornire una spiegazione quando si verifica la situazione. Vedere la "media ponderata quando il peso totale è zero" nell'esempio seguente.

C'è un modo per "invertire" un test di underflow di un divisore. Matematicamente se b non è zero e
abs(a) / abs(b) > abs(c) dove c è il più grande valore in virgola mobile rappresentabile, quindi
abs(a) > abs(c) * abs(b) . Tuttavia, in pratica richiede un'implementazione più attenta di quella . Potresti riuscire a trovare una funzione di libreria matematica che ti consente di passare in (a, b) e restituirà se la divisione avrà un overflow, un underflow o comunque una scarsa precisione.

Gli analizzatori del codice sorgente cercano gli schemi nel codice; non sono abbastanza sofisticati da decidere se la logica di workaround di qualcuno è sufficiente per lo scopo di progettazione dell'applicazione. (In realtà anche il programmatore medio potrebbe non essere qualificato per prendere questa decisione.) Gli analizzatori del codice sorgente dovrebbero essere aumentati con una persona qualificata per prendere questa decisione.

Un denominatore di zero può verificarsi in molte manipolazioni matematiche: formule, serie infinite (sommatoria della sequenza), ecc. Esistono molti metodi matematici per calcolare il risultato nonostante abbiano denominatori che si avvicinano a zero (cioè non esattamente zero, ma sono più piccoli del valore rappresentabile dalla macchina). Ciò significa che le formule non devono essere valutate letteralmente - vengono trasformate usando alcuni metodi di calcolo e per ciascuna formula possono esserci diverse versioni alternative che vengono scelte per evitare il problema della divisione per zero.

Un'altra situazione sorge nella media ponderata dei dati. Se si esegue una query che seleziona un sottoinsieme di dati e quando:

  1. la somma dei pesi per il sottoinsieme di dati risulta essere zero o
  2. quando il sottoinsieme è effettivamente vuoto, cioè la query non restituisce alcun risultato

quindi il modo corretto per esprimere questa situazione è "campioni insufficienti (dati) per la query", ecc.

Nella trigonometria di base, alcune rappresentazioni (pendenza) sono molto sensibili ai problemi di divisione, mentre una rappresentazione alternativa (portante, cioè l'angolo) non sarebbe sensibile. Ad esempio, per rappresentare una linea su un piano 2D, in cui le linee verticali e quasi verticali devono essere rappresentate con la stessa robustezza delle linee orizzontali e quasi orizzontali, è possibile:

  1. avere un alternanza tra le linee che sono ripide rispetto a quelle che non lo sono. Per le linee più ripide di 45 gradi, dovresti usare (x / y) invece di (y / x) come pendenza "capovolta" della linea, in modo da evitare la divisione con piccoli numeri.
  2. Utilizza una rappresentazione alternativa come a*x + b*y + c == 0 e memorizza i parametri (a, b, c) con il requisito che (a^2 + b^2) deve essere uguale a 1.0 per i casi normali e 0.0 se la linea è degenerata (non-a-line).

Vale la pena ricordare che la degenerazione è inevitabile in molti contesti diversi (e in modi specifici del contesto). Ad esempio, se l'utente passa una "riga" from point (x1, y1) to point (x2, y2) e chiede di calcolarne la pendenza, e succede che (x1 == x2 and y1 == y2) , quindi non c'è pendenza, perché non c'è linea, perché c'è solo un punto nel l'input dell'utente.

    
risposta data 05.02.2016 - 16:56
fonte
2

Domanda 1) Non la chiamerei una "teoria", ma ci sono molte conoscenze pragmatiche nel calcolo statistico e scientifico sulla gestione della divisione per 0 o sul log di 0. Non c'è una "teoria" , perché il modo in cui lo gestisci dipende interamente dal contesto. A volte è del tutto normale e auspicabile che una quantità calcolata raggiunga esattamente lo 0. Può solo indicare che la soluzione è completamente convergente. In altri casi potrebbe significare che stai tentando di applicare un algoritmo al di fuori del suo dominio valido e dovresti ricollocare il tuo problema.

Domanda 2) Una delle operazioni più comuni nelle statistiche è quella di dividere una probabilità per un'altra per formare un rapporto di probabilità. Entrambe queste probabilità potrebbero essere arbitrariamente piccole. Tuttavia, affermare che un evento ha una probabilità esattamente pari a 0 è un'asserzione assurdamente strong nella maggior parte delle circostanze, quindi quando si imposta il problema è possibile aggiungere un numero molto piccolo a tutte le probabilità di input. Questo rappresenta la nostra conoscenza "precedente" che nessuno degli eventi è assolutamente impossibile. Questo ha anche l'effetto di impedire la divisione per 0, o di prendere il registro di 0 quando si calcolano i rapporti di quota.

    
risposta data 06.02.2016 - 00:08
fonte
1

Penso che questo sia abbastanza fuorviante. Nella maggior parte delle implementazioni al giorno d'oggi, la divisione per zero ti darà l'infinito, che si comporta molto come un numero molto, molto grande. Se hai un quoziente che potrebbe diventare zero, allora diventerà molto vicino allo zero, quindi il tuo quoziente sarà molto grande, quindi hai un problema - che devi risolvere, se controlli la divisione per zero o meno.

Se calcoli una funzione come sin (x) / x, dove sia il numeratore che il denominatore possono diventare zero simultaneamente, allora il test per x == 0 è assolutamente buono (e ovviamente devi restituire 1.0 in quel caso, ma se calcoli (2 sin x) / x, devi restituire 2.0 Se calcoli x / sin (x) e x è vicino a un multiplo di pi, quindi verifica la divisione per zero eccetto quando x = 0 è solo sbagliato .

    
risposta data 05.02.2016 - 16:58
fonte
0

a) Se la divisione per zero è un problema, anche gli overflow sono un problema.

b) Se hai i controlli per evitare gli overflow, non hai bisogno di controlli per evitare la divisione per zero

c) Non devi mai preoccuparti della divisione per zero.

d) Fare if( divisor == 0) prima della divisione è sempre inutile o sbagliato.

    
risposta data 05.02.2016 - 21:55
fonte

Leggi altre domande sui tag