2011-01-12 17 views
33

TimeSpan.FromSeconds prende un doppio e può rappresentare valori fino a 100 nanosecondi, tuttavia questo metodo inspiegabilmente arrotonda il tempo a interi millisecondi.Perché TimeSpan.FromSeconds (double) round in millisecondi?

Dato che ho appena trascorso mezz'ora per individuare questo comportamento (documentato!), Sapere perché questo potrebbe essere il caso renderebbe più facile sopportare il tempo sprecato.

Qualcuno può suggerire perché questo comportamento apparentemente controproducente è implementato?

TimeSpan.FromSeconds(0.12345678).TotalSeconds 
    // 0.123 
TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond * 0.12345678)).TotalSeconds 
    // 0.1234567 
+0

Mi dispiace vedere la vecchia risposta andare via. Dubito che potrei inventarmi qualcosa di meglio (e volevo farlo +1 ...) – Peter

+0

"[K] considerando il motivo per cui questo potrebbe essere il caso renderebbe più facile sopportare il tempo perso". Consideralo un costo affondato. – jason

+1

Anche morso da quello. La mia teoria è che si trattava di un bug in .net 1 e non è stato modificato perché avrebbe interrotto i programmi esistenti. IMO MS dovrebbe almeno aggiornare la descrizione intellisense per indicare che queste funzioni hanno solo millisecondi di precisione. – CodesInChaos

risposta

11

Come hai scoperto tu stesso, è una funzionalità documentata. E 'descritto nel documentation of TimeSpan:

Parametri

valore Tipo: System.Double

Un numero di secondi, accurato al millisecondo più vicino.

La ragione di questo è probabilmente perché un doppio non è affatto preciso. È sempre una buona idea fare qualche arrotondamento quando si confrontano i doppi, perché potrebbe essere solo un po 'più grande o più piccolo di quanto ci si aspetterebbe. Quel comportamento potrebbe effettivamente fornire alcuni nanosecondi imprevisti quando si tenta di inserire interi millisecondi. Penso che sia la ragione per cui hanno scelto di arrotondare il valore a interi millisecondi e scartare le cifre più piccole.

+0

"Inaspettati nanosecondi" suona come una teoria plausibile. Ancora non lo giustifica nel mio libro, ma questo è oltre il punto. +1. –

+0

Sì, ma i millisecondi saranno probabilmente sufficienti per la maggior parte delle persone. Le persone che sono abbastanza intelligenti da lavorare con nanosecondi possono usare 'TimeSpan.FromTicks (TimeSpan.TicksPerSecond * YourSecondsDouble)'. ;) – GolezTrol

0

FromSeconds utilizza metodo privato Interval

public static TimeSpan FromSeconds(double value) 
{ 
    return Interval(value, 0x3e8); 
} 

0x3e8 == 1000

Intervallo valore metodo multiplay su quel const e poi gettato a lungo (vedi ultima riga):

private static TimeSpan Interval(double value, int scale) 
{ 
    if (double.IsNaN(value)) 
    { 
     throw new ArgumentException(Environment.GetResourceString("Arg_CannotBeNaN")); 
    } 
    double num = value * scale; // Multiply!!!! 
    double num2 = num + ((value >= 0.0) ? 0.5 : -0.5); 
    if ((num2 > 922337203685477) || (num2 < -922337203685477)) 
    { 
     throw new OverflowException(Environment.GetResourceString("Overflow_TimeSpanTooLong")); 
    } 
    return new TimeSpan(((long) num2) * 0x2710L); // Cast to long!!! 
} 

Come risultato abbiamo precisione con 3 (x1000) i segni. Usa riflettore per indagare

+0

Ho fatto ovviamente un'occhiata. Reflector può sempre rispondere a _how_, ma non a _why_. Questo ti ha aiutato a scoprire perché il cast si verifica a lungo ** prima ** della moltiplicazione?Considererei un errore se non fosse stato documentato. Forse era ancora un bug che era più facile documentare che correggere. In conclusione, questo codice non risponde alla domanda "perché". –

+1

@romkyns "Un bug è più facile documentare che correggere!" Penso che tu abbia appena trovato il nuovo slogan di Microsoft! ;-) –

+0

@Nicolas: Come frase a parte questo suona divertente. Ma non penso che stiamo avendo un bug _unexpected_ qui. Questo disegno potrebbe essere intenzionale. –

7

Sui diritti di una speculazione ..

  1. TimeSpan.MaxValue.TotalMilliseconds è equat a 922337203685477. Il numero che ha 15 cifre.
  2. double è preciso a 15 cifre.
  3. TimeSpan.FromSeconds, TimeSpan.FromMinutes ecc tutti passare attraverso la conversione in millisecondi espressi in double (quindi alle zecche poi a TimeSpan che non è interessante ora)

Così, quando si sta creando TimeSpan che sarà vicino alla TimeSpan.MaxValue (oppure MinValue) la conversione sarà precisa a millisecondi solo.
Quindi la risposta probabile alla domanda "perché" è: per avere la precisione stessa tutte le volte.
Un'altra cosa a cui pensare è se il lavoro avrebbe potuto essere fatto meglio se le conversioni fossero state fatte convertendo inizialmente il valore in zecche espresse in long.

+0

Secondo i documenti, il valore * è * convertito in tick prima. – GolezTrol

+0

@ Golez: dove hai visto tale documento? [Il parametro value viene convertito in millisecondi, che viene convertito in tick e quel numero di tick viene utilizzato per inizializzare il nuovo TimeSpan] (http://msdn.microsoft.com/en-us/library/system.timespan.fromseconds .aspx) –

+0

Mi dispiace, ho letto male. – GolezTrol

4

Immagina di essere lo sviluppatore responsabile della progettazione del tipo TimeSpan. Hai tutte le funzionalità di base sul posto; tutto sembra funzionare alla grande. Poi un giorno un po 'di beta tester arriva e vi mostra questo codice:

double x = 100000000000000; 
double y = 0.5; 
TimeSpan t1 = TimeSpan.FromMilliseconds(x + y); 
TimeSpan t2 = TimeSpan.FromMilliseconds(x) + TimeSpan.FromMilliseconds(y); 
Console.WriteLine(t1 == t2); 

Perché quell'uscita False? il tester ti chiede. Anche se capisci perché questo è accaduto (la perdita di precisione nell'aggiungere insieme x e), devi ammettere che lo sembra un po 'strano dal punto di vista del cliente. Poi si lancia questo a voi:

x = 10.0; 
y = 0.5; 
t1 = TimeSpan.FromMilliseconds(x + y); 
t2 = TimeSpan.FromMilliseconds(x) + TimeSpan.FromMilliseconds(y); 
Console.WriteLine(t1 == t2); 

Che uno uscite True! Il tester è comprensibilmente scettico.

A questo punto si deve prendere una decisione. O si può consentire un'operazione aritmetica tra TimeSpan valori che sono stati costruiti da double valori per produrre un risultato cui precisione supera la precisione del tipo double stessa -eg, 100000000000000.5 (16 cifre significative) -o si può, sapere, non consentire quello.

Quindi si decide, sapete cosa, farò in modo che qualsiasi metodo che utilizza un double per costruire un TimeSpan sarà arrotondato al millisecondo più vicino. In questo modo, è documentata esplicitamente che la conversione da un double ad un TimeSpan è un'operazione lossy, mi assolvendo nei casi in cui un cliente vede un comportamento strano come questo dopo la conversione double-TimeSpan e sperando in un risultato preciso.

Non sto necessariamente sostenendo che questa è la "giusta" decisione qui; chiaramente, questo approccio provoca una certa confusione da solo. Sto solo dicendo che una decisione doveva essere presa in un modo o nell'altro, e questo è ciò che apparentemente è stato deciso.

+0

Si potrebbe usare proprio questo argomento per mettere fuori legge i doppi del tutto ... Ma poi, posso immaginare che i designer prestino un'indebita attenzione ai principianti assoluti mentre testano questa specifica classe ... Vorrei poter sapere con certezza come questo è venuto alla luce:) –