Verifica se un numero in virgola mobile è un numero intero
-
02-07-2019 - |
Domanda
Questo codice funziona (C # 3)
double d;
if(d == (double)(int)d) ...;
- C'è un modo migliore per farlo?
- Per motivi estranei voglio evitare così il doppio cast; che bei modi esistono oltre a questo? (anche se non sono così buoni)
Nota: Diverse persone hanno sottolineato il punto (importante) che == è spesso problematico in termini di virgola mobile. In questi casi mi aspetto valori nell'intervallo da 0 a qualche centinaio e si suppone che siano numeri interi (i non ints sono errori) quindi se quei punti & Quot; non dovrebbero & Quot; essere un problema per me.
Soluzione
d == Math.Floor(d)
fa la stessa cosa in altre parole.
NB: Spero che tu sappia che devi fare molta attenzione quando fai questo tipo di cose; float / doppi accumuleranno molto facilmente errori minuscoli che fanno fallire confronti esatti (come questo) senza motivo ovvio.
Altri suggerimenti
Se il tuo doppio è il risultato di un altro calcolo, probabilmente vuoi qualcosa del tipo:
d == Math.Floor(d + 0.00001);
In questo modo, se si è verificato un leggero errore di arrotondamento, corrisponderà comunque.
Penso che funzionerebbe:
if (d % 1 == 0) {
//...
}
Non posso rispondere alla parte specifica della domanda in C #, ma devo sottolineare che probabilmente manca un problema generico con numeri in virgola mobile.
Generalmente, l'intelligenza non è ben definita sui float. Per lo stesso motivo che l'uguaglianza non è ben definita sui float. I calcoli in virgola mobile normalmente includono errori di arrotondamento e di rappresentazione.
Ad esempio, 1.1 + 0.6 != 1.7
.
Sì, è così che funzionano i numeri in virgola mobile.
Qui, 1.1 + 0.6 - 1.7 == 2.2204460492503131e-16
.
A rigor di termini, la cosa più vicina al confronto di uguaglianza che puoi fare con i float è confrontarli fino ad una precisione scelta .
Se ciò non è sufficiente, è necessario lavorare con una rappresentazione numerica decimale, con una rappresentazione numerica in virgola mobile con intervallo di errori incorporato o con calcoli simbolici.
Se hai intenzione di convertirlo, la risposta di Mike F / Khoth è buona, ma non risponde alla tua domanda. Se hai intenzione di testare ed è effettivamente importante, ti consiglio di implementare qualcosa che includa un margine di errore.
Ad esempio, se stai considerando il denaro e vuoi testare anche gli importi in dollari, potresti dire (seguendo il modello di Khoth):
if( Math.abs(d - Math.Floor(d + 0.001)) < 0.001)
In altre parole, prendi il valore assoluto della differenza del valore e la sua rappresentazione intera e assicurati che sia piccola.
Non è necessario il extra (doppio) lì dentro. Questo funziona:
if (d == (int)d) {
//...
}
Usa Math.Truncate ()
Un semplice test come 'x == floor (x)' è matematicamente garantito per funzionare correttamente, per qualsiasi numero FP a precisione fissa.
Tutte le codifiche FP a precisione fissa legali rappresentano numeri reali distinti, quindi per ogni numero intero x esiste al massimo una codifica FP a precisione fissa che corrisponde esattamente.
Pertanto, per ogni intero x che PUO 'essere rappresentato in questo modo, abbiamo x == floor (x) necessariamente, poiché floor (x) per definizione restituisce il numero FP più grande y tale che y < = xey rappresenta un numero intero; quindi floor (x) deve restituire x.
Questo ti permetterà di scegliere quale precisione stai cercando, più o meno mezzo segno di spunta, per tenere conto della deriva in virgola mobile. Anche il confronto è integrale, il che è carino.
static void Main(string[] args)
{
const int precision = 10000;
foreach (var d in new[] { 2, 2.9, 2.001, 1.999, 1.99999999, 2.00000001 })
{
if ((int) (d*precision + .5)%precision == 0)
{
Console.WriteLine("{0} is an int", d);
}
}
}
e l'output è
2 is an int
1.99999999 is an int
2.00000001 is an int
Qualcosa del genere
double d = 4.0;
int i = 4;
bool equal = d.CompareTo(i) == 0; // true
Potresti usare questo
bool IsInt(double x)
{
try
{
int y = Int16.Parse(x.ToString());
return true;
}
catch
{
return false;
}
}
Per gestire la precisione del doppio ...
Math.Abs(d - Math.Floor(d)) <= double.Epsilon
Considera il seguente caso in cui un valore inferiore a double.Epsilon non riesce a confrontare come zero.
// number of possible rounds
const int rounds = 1;
// precision causes rounding up to double.Epsilon
double d = double.Epsilon*.75;
// due to the rounding this comparison fails
Console.WriteLine(d == Math.Floor(d));
// this comparison succeeds by accounting for the rounding
Console.WriteLine(Math.Abs(d - Math.Floor(d)) <= rounds*double.Epsilon);
// The difference is double.Epsilon, 4.940656458412465E-324
Console.WriteLine(Math.Abs(d - Math.Floor(d)).ToString("E15"));