Почему TimeSpan.FromSeconds(двойной) округляется до миллисекунды?

TimeSpan.FromSeconds принимает двойной и может представлять значения до 100 наносекунд, однако этот метод необъяснимо округляет время до целых миллисекунд.

Учитывая, что я только что потратил полчаса, чтобы определить это (документированное!) поведение, зная, почему это может быть так, было бы легче мириться с потерянным временем.

Может ли кто-нибудь предположить, почему это, казалось бы, контрпродуктивное поведение реализовано?

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

Ответ 1

Как вы сами узнали, это документально подтвержденная функция. Он описан в документации TimeSpan:

Параметры

Значение Тип: System.Double

Число секунд, с точностью до ближайшей миллисекунды.

Причина этого, вероятно, состоит в том, что двойной не совсем точный. Всегда полезно совершать округление при сравнении удвоений, потому что это может быть просто крошечный бит больше или меньше, чем вы ожидали. Это поведение действительно может дать вам неожиданные наносекунды, когда вы попытаетесь ввести целые миллисекунды. Я думаю, что именно поэтому они решили округлить значение до целых миллисекунд и отбросить меньшие цифры.

Ответ 2

О правах спекуляции.

  • TimeSpan.MaxValue.TotalMilliseconds - equat to 922337203685477. Число, которое имеет 15 цифр.
  • double является точным до 15 цифр.
  • TimeSpan.FromSeconds, TimeSpan.FromMinutes и т.д. все проходят преобразование в миллисекунды, выраженное в double (затем тикает до TimeSpan, что сейчас не интересно)

Итак, когда вы создаете TimeSpan, который будет близок к TimeSpan.MaxValue (или MinValue), преобразование будет точным только в миллисекундах.
Поэтому вероятный ответ на вопрос "почему" : , чтобы иметь ту же точность все время.
Дальнейшая вещь, о которой нужно подумать, заключается в том, можно ли было сделать работу лучше, если бы конверсии были сделаны, сначала преобразуя значение в тики, выраженные в long.

Ответ 3

Представьте, что вы разработчик, отвечающий за разработку типа TimeSpan. У вас есть все основные функциональные возможности; все это работает отлично. Затем в один прекрасный день появляется несколько бета-тестеров и показывает вам этот код:

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

Почему этот вывод False? тестер спрашивает вас. Даже если вы понимаете, почему это произошло (потеря точности при объединении x и y), вы должны признать, что это кажется немного странным с точки зрения клиента. Затем он бросает это на вас:

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

Этот выводит True! Тестер по-видимому скептически настроен.

На этом этапе вы должны принять решение. Либо вы можете разрешить арифметическую операцию между значениями TimeSpan, которые были построены из значений double, чтобы получить результат , точность которого превышает точность самого типа double -eg, 100000000000000.5 (16 знаменательные цифры), или вы можете, знаете ли, не допускать этого.

Итак, вы решили, что знаете, я просто сделаю так, чтобы любой метод, который использует double для построения TimeSpan, будет округлен до ближайшей миллисекунды. Таким образом, явно указано, что преобразование из double в TimeSpan является операцией с потерями, освобождая меня в случаях, когда клиент видит странное поведение, подобное этому после преобразования от double до TimeSpan, и надеясь на точный результат.

Я не обязательно утверждаю, что это "правильное" решение здесь; Очевидно, что этот подход вызывает определенную путаницу. Я просто говорю, что решение должно быть принято так или иначе, и это то, что, по-видимому, было решено.

Ответ 5

FromSeconds использует закрытый метод Интервал

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

0x3e8 == 1000

Значение интервала умножения на этой константе, а затем длительность (см. последнюю строку):

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

В результате мы имеем точность с 3 (x1000) знаками. Используйте рефлектор для исследования