2009-02-19 14 views
15

Ho una semplice funzione C#:problemi doppie precisione su NET

public static double Floor(double value, double step) 
{ 
    return Math.Floor(value/step) * step; 
} 

che calcola il numero maggiore, minore o uguale a "valore", cioè multiplo di "step". Ma manca di precisione, come si vede nelle seguenti prove:

[TestMethod()] 
public void FloorTest() 
{ 
    int decimals = 6; 
    double value = 5F; 
    double step = 2F; 
    double expected = 4F; 
    double actual = Class.Floor(value, step); 
    Assert.AreEqual(expected, actual); 
    value = -11.5F; 
    step = 1.1F; 
    expected = -12.1F; 
    actual = Class.Floor(value, step); 
    Assert.AreEqual(Math.Round(expected, decimals),Math.Round(actual, decimals)); 
    Assert.AreEqual(expected, actual); 
} 

La prima e la seconda afferma sono ok, ma il terzo fallisce, perché il risultato è pari solo fino al decimale 6a. Perché? C'è un modo per correggere questo?

Aggiornamento Se eseguo il debug del test, vedo che i valori sono uguali fino all'ottavo decimale anziché al sesto, forse perché Math.Round introduce qualche imprecisione.

Nel mio codice di test ho scritto il suffisso "F" (costante float esplicita) dove intendevo "D" (doppio), quindi se lo cambio posso avere più precisione.

risposta

5

Se si omettono tutti i postfix F (ad esempio anziché -12.1F) si otterrà l'uguaglianza di alcune cifre in più. Le tue costanti (e in particolare i valori previsti) ora sono float a causa dello F. Se lo fai apposta, per favore spiega.

Ma per il resto concordo con le altre risposte sul confronto tra valori doppi o float per l'uguaglianza, non è affidabile.

+0

ma la F maiuscola significa doppio, non float, giusto? è la f minuscola che significa galleggiare. –

+0

No, ho appena controllato: float x = 1.0; dà un errore, float x = 1.0F; è OK. La F non è sensibile al maiuscolo/minuscolo. –

+1

E ha cercato in Ecmea334: 1.0D per il doppio, 1.0 M per decimale. –

8

aritmetica in virgola mobile su computer non sono esatti Science :).

Se si desidera una precisione esatta per un numero predefinito di decimali, utilizzare Decimale anziché doppio o accettare un intervallo minore.

+5

È una scienza esatta all'interno del numero definito IEEE di cifre significative. –

+1

Per rinforzare quanto sopra: numeri in virgola mobile _are_ exact. Il numero che desideri potrebbe non essere rappresentato come un numero in virgola mobile IEEE, il che significa che devi aliasare il numero sul successivo più vicino, il che porta all'errore, ma ciò non significa che i numeri che _can_ rappresentano abbiano errore in loro. – codekaizen

+2

Inoltre, i decimali possono soffrire dello stesso problema di Doubles, poiché anch'essi sono in virgola mobile. Possono incontrare lo stesso problema di rappresentazione, ma è molto meno inaspettato, dato che hanno una base di 10, contro una base di 2, e siamo abituati ad affrontare problemi di rappresentazione in base 10 (ad esempio 1/3 è 0.333333 .. . nella base 10). – codekaizen

1

galleggianti e doppi non possono memorizzare tutti i numeri con precisione. Questa è una limitazione con il sistema in virgola mobile IEEE. Per avere una precisione fedele è necessario utilizzare una libreria matematica più avanzata.

Se non hai bisogno di precisione oltre un certo punto, forse decimale funzionerà meglio per te. Ha una precisione più alta del doppio.

4

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Ad esempio, la non rappresentabilità di 0,1 e 0,01 (in binario) significa che il risultato del tentativo di piazzare 0.1 è né 0,01 né il numero rappresentabile più vicino ad esso.

Utilizzare il virgola mobile solo se si desidera l'interpretazione (binaria) di un sistema numerico della macchina. Non puoi rappresentare 10 centesimi.

4

Se si desidera precisione, utilizzare System.Decimal. Se vuoi velocità, usa System.Double (o System.Float). I numeri in virgola mobile non sono numeri di "precisione infinita" e pertanto l'affermazione dell'uguaglianza deve includere una tolleranza. Finché i tuoi numeri hanno un numero ragionevole di cifre significative, questo è ok.

  • Se stai cercando di fare calcoli su numeri molto piccoli e molto piccoli, non utilizzare float o double.
  • Se hai bisogno di precisione infinita, non utilizzare float o double.
  • Se si aggrega un numero molto elevato di valori, non utilizzare float o double (gli errori si comporteranno da soli).
  • Se hai bisogno di velocità e dimensioni, usa float o double.

Vedere la risposta this (anche da parte mia) per un'analisi dettagliata di come la precisione influisce sul risultato delle operazioni matematiche.

+0

Non esiste una 'precisione infinita'. Il problema con float/double è che sono precisi per un numero di cifre binarie e non per un numero di cifre decimali. – configurator

+2

Esiste una precisione infinita.Un numero intero è un tipo infinitamente preciso. Non perde precisione durante le operazioni matematiche. È possibile implementare un tipo decimale infinitamente preciso (anche se molto inefficiente), ma non è "out of the box" in .Net. –

+0

Michael, il tuo commento è stupido. Definito in questo modo, in modo che si possa dire che gli interi hanno "Precisione infinita" solo perché non cambiano durante un'operazione matematica, quindi ogni numero ha "Precisione infinita", (i fluttuanti, i doppi ei decimali non cambiano neanche durante le operazioni matematiche) . Definito in questo modo, il concetto perde ogni significato. Diamine, anche solo un segno con solo due valori, (Positivo o Negativo) ha "Infinite Precision", perché, secondo la tua definizione, non perde precisione durante qualsiasi operazione matematica in cui è usato. –

0

Per il problema simile, io alla fine con la seguente applicazione che sembra successo la maggior parte del mio banco di prova (fino a 5 cifre di precisione):

public static double roundValue(double rawValue, double valueTick) 
{ 
    if (valueTick <= 0.0) return 0.0; 

    Decimal val = new Decimal(rawValue); 
    Decimal step = new Decimal(valueTick); 
    Decimal modulo = Decimal.Round(Decimal.Divide(val,step)); 

    return Decimal.ToDouble(Decimal.Multiply(modulo, step)); 
} 
8

Io in realtà una sorta di vorrei che non avevano attuato l'operatore == per float e double. È quasi sempre la cosa sbagliata da fare per chiedere se un doppio o un galleggiante è uguale a qualsiasi altro valore.

+1

SI! SÌ! SÌ! Lo sto dicendo da un po 'ora. È come l'intero 0.999 ... = 1.0 problema. (1.0 - 0.000 ... = 1.0). I punti mobili sono un animale completamente diverso rispetto agli interi. – Josh

0

A volte il risultato è più preciso di quanto ci si aspetterebbe da rigoroso: FP IEEE 754. Questo perché HW utilizza più bit per il calcolo. Vedere C# specification e this article

Java ha una parola chiave strictfp e C++ hanno opzioni di compilazione. Mi manca questa opzione in .NET

Problemi correlati