Cosa si può fare con i linguaggi di programmazione per evitare le insidie in virgola mobile?

27

L'incomprensione dell'aritmetica in virgola mobile e le sue carenze sono una delle principali cause di sorpresa e confusione nella programmazione (considerare il numero di domande su Stack Overflow relative a "numeri che non si aggiungono correttamente"). Considerando che molti programmatori non hanno ancora capito le sue implicazioni, ha il potenziale di introdurre molti bug sottili (specialmente nel software finanziario). Cosa possono fare i linguaggi di programmazione per evitare le insidie di chi non ha familiarità con i concetti, pur offrendo la sua velocità quando la precisione non è fondamentale per coloro che fanno capiscono i concetti?

    
posta Adam Paynter 28.03.2011 - 18:36
fonte

16 risposte

45

Dici "soprattutto per il software finanziario", che fa apparire uno dei miei animaletti: il denaro non è un float, è un int .

Certo, sembra un galleggiante. Ha un punto decimale lì. Ma è solo perché sei abituato a unità che confondono il problema. Il denaro arriva sempre in quantità intere. In America, è centesimi. (In alcuni contesti penso che possa essere macinato , ma ignoralo per ora.)

Quindi quando dici $ 1,23, sono davvero 123 centesimi. Sempre, sempre, fai sempre i tuoi calcoli in questi termini e starai bene. Per ulteriori informazioni, vedi:

Rispondendo direttamente alla domanda, i linguaggi di programmazione dovrebbero includere solo un tipo di Money come una primitiva ragionevole.

Aggiorna

Ok, avrei dovuto dire "sempre" solo due volte, anziché tre volte. Il denaro è infatti sempre un int; coloro che pensano diversamente sono invitati a provare a mandarmi 0,3 centesimi e a mostrarmi il risultato sul conto bancario. Ma come sottolineano i commentatori, ci sono rare eccezioni quando devi fare matematica in virgola mobile su numeri simili a quelli di denaro. Ad esempio, determinati tipi di prezzi o calcoli di interesse. Anche allora, quelli dovrebbero essere trattati come eccezioni. Il denaro entra e esce come numero intero, quindi più il tuo sistema si avvicina a quello, più sano sarà.

    
risposta data 31.03.2011 - 18:54
fonte
15

Fornire supporto per un tipo decimale aiuta in molti casi. Molte lingue hanno un tipo decimale, ma sono sottoutilizzate.

Capire l'approssimazione che si verifica quando si lavora con la rappresentazione di numeri reali è importante. L'utilizzo di entrambi i tipi decimale e in virgola mobile 9 * (1/9) != 1 è un'istruzione corretta. Quando le costanti un ottimizzatore possono ottimizzare il calcolo in modo che sia corretto.

Fornire un operatore approssimativo sarebbe d'aiuto. Tuttavia, tali confronti sono problematici. Si noti che .9999 miliardi di dollari equivalgono a circa 1 trilione di dollari. Potresti depositare la differenza sul mio conto bancario?

    
risposta data 28.03.2011 - 19:33
fonte
8

Ci è stato detto cosa fare nella prima lezione del secondo anno in informatica quando sono andato all'università, (questo corso era anche un prerequisito per la maggior parte dei corsi di scienze)

Ricordo il docente che diceva "I numeri in virgola mobile sono approssimazioni." Usa tipi interi per soldi. Usa FORTRAN o altra lingua con numeri BCD per calcoli accurati. " (e poi ha indicato l'approssimazione, usando quel classico esempio di 0.2 impossibile da rappresentare con precisione in virgola mobile binario). Questo si è presentato anche questa settimana negli esercizi di laboratorio.

Stessa lezione: "Se devi ottenere maggiore precisione dal punto mobile, ordina i termini. Aggiungi numeri piccoli insieme, non a numeri grandi". Mi è rimasto impresso nella mente.

Alcuni anni fa avevo una geometria sferica che doveva essere molto precisa e comunque veloce. 80 bit double su PC non lo stava tagliando, quindi ho aggiunto alcuni tipi al programma che ordinava i termini prima di eseguire operazioni commutative. Problema risolto.

Prima di lamentarti della qualità della chitarra, impara a suonare.

Ho avuto un collega di lavoro quattro anni fa che aveva lavorato per JPL. Ha espresso incredulo che abbiamo usato FORTRAN per alcune cose. (Avevamo bisogno di simulazioni numeriche super precise calcolate offline.) "Abbiamo sostituito tutto quello FORTRAN con il C ++", disse con orgoglio. Ho smesso di chiedermi perché hanno perso un pianeta.

    
risposta data 29.03.2011 - 00:29
fonte
8

Warning: The floating-point type System.Double lacks the precision for direct equality testing.

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

Non credo che nulla possa o dovrebbe essere fatto a livello linguistico.

    
risposta data 01.08.2012 - 15:04
fonte
6

Per impostazione predefinita, le lingue devono utilizzare razionali a precisione arbitraria per numeri non interi.

Chi ha bisogno di ottimizzare può sempre chiedere dei galleggianti. Usarli come predefinito aveva senso in C e in altri linguaggi di programmazione dei sistemi, ma non nella maggior parte delle lingue popolari oggi.

    
risposta data 28.03.2011 - 20:07
fonte
4

I due problemi principali che coinvolgono i numeri in virgola mobile sono:

  • unità incoerenti applicate ai calcoli (nota che questo influenza anche l'aritmetica dei numeri interi allo stesso modo)
  • non capisco che i numeri FP sono una approssimazione e come affrontare in modo intelligente l'arrotondamento.

Il primo tipo di guasto può essere risolto solo fornendo un tipo composito che includa informazioni sul valore e sull'unità. Ad esempio, un valore length o area che incorpora l'unità (metri o metri quadrati o piedi e piedi quadrati, rispettivamente). Altrimenti devi essere diligente nel lavorare sempre con un tipo di unità di misura e convertirti in un altro solo quando condividiamo la risposta con un umano.

Il secondo tipo di fallimento è un fallimento concettuale. I fallimenti si manifestano quando le persone li considerano come numeri assoluti . Colpisce le operazioni di uguaglianza, gli errori di arrotondamento cumulativo, ecc. Ad esempio, può essere corretto che per un sistema due misurazioni siano equivalenti entro un certo margine di errore. Cioè .999 e 1.001 sono circa lo stesso di 1.0 quando non ti preoccupi delle differenze inferiori a +/- .1. Tuttavia, non tutti i sistemi sono così clementi.

Se è necessaria una struttura a livello di lingua, allora la chiamerei precisione dell'uguaglianza . In NUnit, JUnit e framework di test costruiti in modo simile è possibile controllare la precisione considerata corretta. Ad esempio:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

Se, ad esempio, C # o Java sono stati modificati per includere un operatore di precisione, potrebbe essere simile a questo:

if(.999 == 1.001 within .1) { /* do something */ }

Tuttavia, se fornisci una funzione del genere, devi considerare anche il caso in cui l'uguaglianza è buona se i lati +/- non sono gli stessi. Ad esempio, + 1 / -10 considererebbe due numeri equivalenti se uno di questi fosse entro 1 altro o 10 in meno rispetto al primo numero. Per gestire questo caso, potrebbe essere necessario aggiungere anche una parola chiave range :

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }
    
risposta data 28.03.2011 - 18:55
fonte
3

Cosa possono fare i linguaggi di programmazione? Non so se c'è una risposta a questa domanda, perché tutto ciò che il compilatore / interprete fa per conto del programmatore per rendere la sua vita più facile di solito funziona contro le prestazioni, la chiarezza e la leggibilità. Penso che sia il modo C ++ (paghi solo quello di cui hai bisogno) sia il modo Perl (principio di minima sorpresa) sono entrambi validi, ma dipende dall'applicazione.

I programmatori devono ancora lavorare con la lingua e capire come gestiscono i punti mobili, perché se non lo fanno, faranno delle ipotesi, e un giorno il comportamento descritto non corrisponderà alle loro ipotesi.

La mia opinione su ciò che il programmatore deve sapere:

  • Quali tipi di virgola mobile sono disponibili sul sistema e nella lingua
  • Che tipo è necessario
  • Come esprimere le intenzioni di quale tipo è necessario nel codice
  • Come sfruttare correttamente qualsiasi promozione di tipo automatico per bilanciare chiarezza ed efficienza mantenendo la correttezza
risposta data 28.03.2011 - 18:54
fonte
3

What can programming languages do to avoid [floating point] pitfalls...?

Utilizza valori predefiniti sensibili, ad es. supporto integrato per decmials.

Groovy lo fa abbastanza bene, anche se con un po 'di sforzo puoi ancora scrivere codice per introdurre imprecisioni in virgola mobile.

    
risposta data 28.03.2011 - 19:12
fonte
3

Sono d'accordo che non c'è niente da fare a livello di lingua. I programmatori devono capire che i computer sono discreti e limitati e che molti dei concetti matematici rappresentati in essi sono solo approssimazioni.

Non importa il punto in virgola mobile. Uno deve capire che metà dei modelli di bit sono usati per i numeri negativi e che 2 ^ 64 è in realtà piuttosto piccolo per evitare problemi tipici con l'aritmetica dei numeri interi.

    
risposta data 29.03.2011 - 03:01
fonte
3

Una cosa che le lingue potrebbero fare: rimuovere il confronto di uguaglianza da tipi in virgola mobile diversi da un confronto diretto con i valori NAN.

Il test di uguaglianza esiste solo come chiamata di funzione che ha preso i due valori e un delta, o per linguaggi come C # che consentono ai tipi di avere metodi un EqualsTo che prende l'altro valore e il delta.

    
risposta data 12.07.2012 - 23:45
fonte
3

Trovo strano che nessuno abbia indicato il trucco del numero razionale della famiglia Lisp.

Seriamente, apri sbcl e fai questo: (+ 1 3) e ottieni 4. Se fai *( 3 2) ottieni 6. Ora prova (/ 5 3) e ottieni 5/3 o 5 terzi.

Questo dovrebbe aiutare un po 'in alcune situazioni, non dovrebbe?

    
risposta data 31.07.2012 - 20:12
fonte
3

Una cosa che mi piacerebbe vedere sarebbe un riconoscimento del fatto che double a float dovrebbe essere considerato come una conversione allargata, mentre float a double sta restringendo (*). Questo può sembrare contro-intuitivo, ma considera cosa significano in realtà i tipi:

  • 0.1f significa "13.421.773,5 / 134.217.728, più o meno 1 / 268.435.456 o giù di lì".
  • 0.1 significa in realtà 3,602,879,701,896,397 / 36,028,797,018,963,968, più o meno 1 / 72,057,594,037,927,936 circa "

Se uno ha un double che contiene la migliore rappresentazione della quantità "un decimo" e lo converte in float , il risultato sarà "13.421.773.5 / 134.217.728, più o meno 1 / 268.435.456 o giù di lì", che è una descrizione corretta del valore.

Al contrario, se uno ha un float che contiene la migliore rappresentazione della quantità "un decimo" e lo converte in double , il risultato sarà "13.421.773.5 / 134.217.728, più o meno 1 / 72.057.594,037,927,936 o così "- un livello di precisione implicita che è sbagliato per un fattore di oltre 53 milioni.

Sebbene lo standard IEEE-744 richieda l'esecuzione di calcoli a virgola mobile come se ogni numero in virgola mobile rappresenti esattamente la quantità numerica esattamente al centro del suo intervallo, ciò non dovrebbe essere implica che i valori in virgola mobile rappresentano effettivamente quelle quantità numeriche esatte. Piuttosto, il requisito che i valori siano assunti al centro dei loro intervalli deriva da tre fatti: (1) i calcoli devono essere eseguiti come se gli operandi avessero dei valori precisi; (2) ipotesi coerenti e documentate sono più utili di quelle incoerenti o prive di documenti; (3) se si intende fare un'ipotesi coerente, nessun'altra ipotesi coerente può essere migliore di assumere che una quantità rappresenti il centro del suo intervallo.

Per inciso, ricordo che circa 25 anni fa qualcuno ha inventato un pacchetto numerico per C che utilizzava "tipi di intervallo", ciascuno costituito da una coppia di galleggianti a 128 bit; tutti i calcoli sarebbero fatti in modo tale da calcolare il valore minimo e massimo possibile per ogni risultato. Se si eseguiva un grande calcolo iterativo lungo e si otteneva un valore di [12.53401391134 12.53902812673], si poteva essere certi che mentre molte cifre di precisione venivano perse per errori di arrotondamento, il risultato poteva ancora essere ragionevolmente espresso come 12.54 (e non era t veramente 12.9 o 53.2). Sono sorpreso di non aver visto alcun supporto per questi tipi in tutte le lingue mainstream, specialmente dal momento che sembrerebbero un buon abbinamento con le unità matematiche che possono operare su più valori in parallelo.

(*) In pratica, è spesso utile utilizzare valori a doppia precisione per tenere calcoli intermedi quando si lavora con numeri a precisione singola, quindi dover usare un typecast per tutte queste operazioni può essere fastidioso. Le lingue potevano essere d'aiuto avendo un tipo "fuzzy double", che eseguiva i calcoli come double, e poteva essere lanciato liberamente da e verso il singolo; questo sarebbe particolarmente utile se le funzioni che assumono i parametri di tipo double e return double potrebbero essere marcate in modo che generino automaticamente un overload che accetta e restituisce invece "fuzzy double".

    
risposta data 01.08.2012 - 00:49
fonte
2

Se più linguaggi di programmazione prendevano una pagina dai database e permettevano agli sviluppatori di specificare la lunghezza e la precisione dei loro tipi di dati numerici, potevano ridurre sostanzialmente la probabilità di errori correlati a virgola mobile. Se una lingua consentiva a uno sviluppatore di dichiarare una variabile come Float (2), indicando che avevano bisogno di un numero in virgola mobile con due cifre decimali di precisione, poteva eseguire operazioni matematiche in modo molto più sicuro. In tal caso, rappresentando la variabile come un intero internamente e dividendo per 100 prima di esporre il valore, è possibile migliorare la velocità utilizzando i percorsi aritmetici interi più veloci. La semantica di un oggetto Float (2) consentirebbe inoltre agli sviluppatori di evitare la costante necessità di arrotondare i dati prima di emetterli poiché un Float (2) avrebbe intrinsecamente arrotondato i dati a due punti decimali.

Naturalmente, è necessario consentire a uno sviluppatore di chiedere un valore in virgola mobile di massima precisione quando lo sviluppatore deve avere quella precisione. E introdurrebbe problemi in cui espressioni leggermente diverse della stessa operazione matematica producono risultati potenzialmente diversi a causa di operazioni di arrotondamento intermedie quando gli sviluppatori non portano abbastanza precisione nelle loro variabili. Ma almeno nel mondo dei database, quello non sembra essere un affare troppo grande. La maggior parte delle persone non sta effettuando calcoli scientifici che richiedono molta precisione nei risultati intermedi.

    
risposta data 28.03.2011 - 19:06
fonte
1
    le lingue
  • hanno il supporto per il tipo decimale; ovviamente questo non risolve il problema, tuttavia non hai una rappresentazione esatta e finita, ad esempio ⅓;
  • alcuni DB e framework hanno il supporto per il tipo di denaro, in pratica si tratta di un numero di centesimi come intero;
  • ci sono alcune librerie per il supporto dei numeri razionali; risolve il problema di ⅓, ma non risolve il problema di ad esempio √2;

Questi esempi sono applicabili in alcuni casi, ma non rappresentano una soluzione generale per la gestione dei valori float. La vera soluzione è capire il problema e imparare come affrontarlo. Se stai utilizzando calcoli a virgola mobile, dovresti sempre verificare che i tuoi algoritmi siano numericamente stabili . C'è un enorme campo di matematica / informatica che si riferisce al problema. Si chiama Analisi numerica .

    
risposta data 29.03.2011 - 01:05
fonte
1

Come altre risposte hanno notato, l'unico vero modo per evitare le insidie in virgola mobile nel software finanziario è di non usarlo lì. Questo può essere effettivamente fattibile - se fornisci una biblioteca matematica ben progettata ben progettata.

Le funzioni progettate per importare stime a virgola mobile dovrebbero essere chiaramente etichettate come tali e fornite di parametri appropriati per tale operazione, ad esempio:

Finance.importEstimate(float value, Finance roundingStep)

L'unico vero modo per evitare le insidie in virgola mobile in generale è l'educazione: i programmatori devono leggere e capire qualcosa come Cosa dovrebbe sapere ogni programmatore Informazioni sull'aritmetica in virgola mobile .

Alcune cose che potrebbero aiutare, però:

  • Seguirò quelli che chiedono "perché il test di uguaglianza esatta per virgola mobile è legale?"
  • Invece, utilizza una funzione isNear() .
  • Fornisci e incoraggia l'uso di oggetti accumulatore a virgola mobile (che aggiungono sequenze di valori in virgola mobile più stabili del semplice aggiungerli tutti in una variabile a virgola mobile regolare).
risposta data 02.08.2012 - 00:27
fonte
-1

La maggior parte dei programmatori rimarrebbe sorpresa dal fatto che COBOL abbia capito bene ... nella prima versione di COBOL non c'era alcun punto fluttuante, solo decimale e la tradizione in COBOL continuava fino ad oggi che la prima cosa che si pensa quando si dichiara un numero è decimale ... il virgola mobile verrebbe usato solo se ne avessi davvero bisogno. Quando C è arrivato, per qualche ragione, non c'era un tipo decimale primitivo, quindi secondo me, è da lì che sono iniziati tutti i problemi.

    
risposta data 28.03.2011 - 23:44
fonte

Leggi altre domande sui tag