.net timespan bcl

.net - ¿Por qué TimeSpan.FromSeconds(doble) ronda a milisegundos?



bcl (5)

Como ya descubriste, es una característica documentada. Se describe en la documentación de TimeSpan :

Parámetros

Tipo de valor : System.Double

Un número de segundos, exacto al milisegundo más cercano .

La razón de esto es probablemente porque un doble no es tan preciso en absoluto. Siempre es una buena idea hacer un poco de redondeo al comparar los dobles porque podría ser un poco más grande o más pequeño de lo que cabría esperar. Ese comportamiento en realidad podría proporcionarle algunos nanosegundos inesperados cuando intente poner en milisegundos enteros. Creo que esa es la razón por la que eligieron redondear el valor a milisegundos enteros y descartar los dígitos más pequeños.

TimeSpan.FromSeconds toma un doble y puede representar valores de hasta 100 nanosegundos, sin embargo, este método inexplicablemente redondea el tiempo a milisegundos enteros.

Dado que acabo de pasar media hora para identificar este comportamiento (¡documentado!), Saber por qué este podría ser el caso haría que fuera más fácil aguantar el tiempo perdido.

¿Alguien puede sugerir por qué se implementa este comportamiento aparentemente contraproducente?

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



FromSeconds usa el método privado Interval

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

0x3e8 == 1000

Intervalo de método de multiplaje en ese const y luego lanzar a largo (ver última fila):

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!!! }

Como resultado tenemos precisión con 3 (x1000) signos. Use el reflector para investigar


Imagina que eres el desarrollador responsable del diseño del tipo TimeSpan . Tienes toda la funcionalidad básica en su lugar; todo parece estar funcionando bien. Entonces, un día aparece un beta tester que te muestra este código:

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

¿Por qué esa salida es False ? el probador te pregunta. Aunque comprenda por qué sucedió esto (la pérdida de precisión al sumar x e y ), debe admitir que parece un poco extraño desde la perspectiva del cliente. Luego te arroja este:

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

¡Esa muestra True ! El probador es comprensiblemente escéptico.

En este punto, debes tomar una decisión. O bien, puede permitir una operación aritmética entre los valores de TimeSpan que se han construido a partir de valores double para obtener un resultado cuya precisión exceda la precisión del mismo tipo de letra double por ejemplo, 100000000000000.5 (16 cifras significativas), o puede, ya sabe, no permitir ese.

Así que usted decide, usted sabe qué, lo haré de manera que cualquier método que use un double para construir un TimeSpan se redondeará al milisegundo más cercano. De esta forma, está explícitamente documentado que convertir un double en un TimeSpan es una operación con pérdida, absolviéndome en los casos en que un cliente ve un comportamiento extraño como este después de convertir el double en TimeSpan y espera obtener un resultado preciso.

No estoy necesariamente argumentando que esta es la decisión "correcta" aquí; claramente, este enfoque causa cierta confusión por sí mismo. Solo digo que se debe tomar una decisión de una manera u otra, y esto es lo que aparentemente se decidió.


Sobre los derechos de una especulación ..

  1. TimeSpan.MaxValue.TotalMilliseconds es igual a 922337203685477. El número que tiene 15 dígitos.
  2. double es preciso para 15 dígitos.
  3. TimeSpan.FromSeconds , TimeSpan.FromSeconds , etc. pasan por la conversión a milisegundos expresados ​​en double (luego a ticks y luego a TimeSpan cual no es interesante ahora)

Entonces, cuando está creando TimeSpan que estará cerca de TimeSpan.MaxValue (o MinValue ) la conversión será precisa a milisegundos solamente.
Entonces la respuesta probable a la pregunta "por qué" es: tener la misma precisión todas las veces .
Otra cosa en que pensar es si el trabajo podría haber sido mejor si las conversiones se hubieran hecho convirtiendo primero el valor en marcas expresadas en long .