Domanda

Ho una semplice funzione C#:

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

Che calcola il numero più alto, inferiore o uguale a "valore", cioè multiplo di "passo".Ma manca di precisione, come si vede nei seguenti test:

[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 affermazione vanno bene, ma la terza fallisce, perché il risultato è uguale solo fino alla sesta cifra decimale.Perché?C'è un modo per correggere questo problema?

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

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

È stato utile?

Soluzione

Se ometti tutti i suffissi F (es -12.1 invece di -12.1F) otterrai l'uguaglianza con qualche cifra in più.Le tue costanti (e in particolare i valori attesi) ora sono float a causa di F.Se lo fai apposta, per favore spiegalo.

Ma per il resto sono d'accordo con le altre risposte sul confronto dei valori double o float per l'uguaglianza, semplicemente non è affidabile.

Altri suggerimenti

In realtà vorrei che non avessero implementato l'operatore == per float e double.È quasi sempre la cosa sbagliata da fare chiedere se un double o un float è uguale a qualsiasi altro valore.

L'aritmetica in virgola mobile sui computer non è scienza esatta :).

Se desideri la precisione esatta su un numero predefinito di decimali, utilizza Decimal invece di double o accetta un intervallo minore.

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 elevare al quadrato 0,1 non è né 0,01 né il numero rappresentabile più vicino ad esso.

Utilizzare la virgola mobile solo se si desidera l'interpretazione di una macchina (binaria) dei sistemi numerici.Non puoi rappresentare 10 centesimi.

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

  • Se stai cercando di fare matematica su numeri molto grandi E molto piccoli, non usare float o double.
  • Se hai bisogno di una precisione infinita, non usare float o double.
  • Se stai aggregando un numero molto elevato di valori, non utilizzare float o double (gli errori si aggraveranno).
  • Se hai bisogno di velocità e dimensioni, usa float o double.

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

Controlla le risposte a questa domanda: È sicuro controllare che i valori in virgola mobile siano uguali a 0?

Davvero, basta controllare "entro la tolleranza di..."

float e double non possono memorizzare con precisione tutti i numeri.Questa è una limitazione del sistema a 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 il decimale funzionerà meglio per te.Ha una precisione maggiore del doppio.

Per un problema simile, finisco per utilizzare la seguente implementazione che sembra avere successo nella maggior parte del mio caso di test (precisione fino a 5 cifre):

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));
}

A volte il risultato è più preciso di quanto ci si aspetterebbe da strict:FP IEEE 754.Questo perché HW utilizza più bit per il calcolo.Vedere Specifica C# E Questo articolo

Java ha la parola chiave strictfp e C++ ha opzioni del compilatore.Mi manca questa opzione in .NET

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top