Как правильно протестировать float или double на качество и другие сравнения

#c# #wpf #linq #equality

#c# #wpf #linq #равенство

Вопрос:

У меня возникли некоторые проблемы с сравнением действительных чисел, хранящихся в виде двойных чисел. Я думаю, что проблемы, скорее всего, вызваны ошибками округления, но я не уверен. Каким был бы наилучший способ сравнить числа, сохраненные как double, и протестированные в linq?

Я получаю время в виде строки из стороннего источника. Похоже, что это секунды с момента преобразования эпохи в реальное время, я уверен, что это в секундах, а не в миллисекундах. Я преобразовываю это в double, используя
double Time = Convert.ToDouble(«1549666889.6220000»); Затем я использую linq для извлечения из списка всех записей, которые охватывают это время

 Infos.Where(x => x.StartTime <= starttime                                                                 
amp;amp; x.EndTime >= starttime).OrderBy(x => x.StartTime).ToList();
  

и результаты, которые я получаю, кажутся выходящими за рамки сравнения, которое я ожидал.
Я ожидал, что возвращаемые элементы — это те, для которых время, которое я тестирую, находится между временем начала и окончания элементов в списке информации.

Я получаю что-то вроде

(к сожалению, следующей партией должна быть таблица времени начала и окончания, но я не могу отформатировать ее в виде таблицы здесь)

Время начала Время окончания 1549665989.622097 1549666889.6221507 1549665989.6690228 1549666889.6790602
1549665989.8786857 1549666889.8817368 1549665989.8926628 154966688.9037011

эти результаты кажутся неправильными, особенно время начала, поскольку они должны быть меньше, чем заданный im временной индекс.

Я думаю, что это проблема с округлением, но не уверен, что это так или моя логика. Если это проблема с округлением, как я должен проводить тестирование в LINQ.

приветствуются любые советы.

Мне только что пришло в голову, может быть, мне следует умножить каждое двойное значение на 10000000, чтобы удалить десятичные дроби и сравнить только целые числа? Это хорошая идея?

Комментарии:

1. Какой результат вы ожидали?

2. Вы следили за значением starttime? Также какой тип x.startTime?

3. Значение времени запуска, которое я получаю из сторонней библиотеки, является правильным, я заметил это. Тип x.starttime — double. Я ожидал, что элементы, возвращенные из списка, где индекс времени, который я тестировал, находится между временем начала и окончания в информации списка.

Ответ №1:

Преобразование строки типа "1549665989.622097" в double приводит к ошибке из-за точности. В этом случае преобразованный double будет 1549665989.6221 .

Если ошибки точности ваших двойных значений являются проблемой, вам следует использовать тип данных decimal:

Ключевое слово decimal указывает на 128-битный тип данных. По сравнению с другими типами с плавающей запятой десятичный тип имеет большую точность и меньший диапазон, что делает его подходящим для финансовых и монетарных расчетов.

Преобразовать.ToDecimal обеспечивает требуемое преобразование из строки. Результат будет 1549665989.622097 без ошибки точности.

Комментарии:

1. Как эта проблема с точностью приведет к возврату элементов, где время начала элемента превышает время, с которым я тестирую. Я пытаюсь вернуть элементы, где startitme находится между x.starttime и x.endtime элементов в списке.

2. Пожалуйста, добавьте несколько примеров ввода и вывода к вашему вопросу.

Ответ №2:

Ваше преобразование неэффективно

Вы понимаете, что вы преобразуете строку startTime в double для вашего Where , и еще много раз для вашего OrderBy , не так ли: OrderBy будет сравнивать 1-й элемент со 2-м, и 1-й с 3-м, и 2-й с 3-м, и 1-й с 4-м, и 2-й с 4-м, и 3-й с 4-м: вы преобразуете свои строки в double снова и снова.

Не было бы эффективнее запомнить это преобразование и повторно использовать преобразованные значения?

Вы преобразуете в неправильный тип

Поскольку мы в любом случае преобразуем сторонние данные, почему бы не преобразовать их в надлежащий объект, представляющий момент времени: System.DateTime ?

Напишите две функции расширения class Info:

 static class InfoExtensions
{
    public static DateTime StartDateTime(this Info info)
    {
        return info.startTime.ToDateTime();
    }

    public static DateTime EndDateTime(this Info info)
    {
        return info.endTime.ToDateTime();
    }

    private static DateTime ToDateTime(this string date3rdParty)
    {
         // ask from your 3rd party what the value means
        // for instance: seconds since some start epoch time:
        static DateTime epochTime = new DateTime(...)

        double secondsSinceEpochTime = Double.Parse(date3rdParty);
        return epochTime.AddSeconds(secondsSinceEpochTime);
    }
}
  

Использование:

 DateTime startTime = ...
var result = Infos.Select(info => new
{
     StartTime = info.StartTime.StartDatetime(),
     EndTime = info.EndTime.EndDateTime(),

     // select the Info properties you actually plan to use:
     ...

     // or select the complete Info:
     Info = info,
})
.Where(info => info.StartTime <= startTime amp;amp; startTime <= info.EndTime)
.OrderBy(info => info.StartTime)

// Only if you prefer to throw away your converted StartTime / EndTime:
.Select(info => info.Info);
  

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

Разделение проблем

Вам следует больше работать над разделением проблем. Если бы вы отделили способ, которым ваша третья сторона представляет свое представление о датах (некоторое строковое представление секунд с некоторой эпохи), от способа, которым вы хотели бы его иметь (вероятно, System.DateTime), тогда у вас не было бы этой проблемы.

Если вы отделите их info класс от своего info класса, ваш код будет более удобен в обслуживании, потому что у вас будет только одно место, где их информационные свойства преобразуются в ваши информационные свойства. Если в будущем они добавят свойства, которые вы не используете, вы этого не заметите. Если они решат изменить свое представление о дате, например, используя другое время эпохи или, возможно, используя систему.Дата и время, будет только одно место, где вам нужно будет изменить свои данные. Также: если появится информация от четвертой стороны: есть только одно место, где вам придется конвертировать.

Разделение эффективно: преобразование выполняется только один раз, независимо от того, как часто вы используете свойство startTime. Например, если в будущем вы хотите, чтобы вся информация была сгруппирована по одной и той же дате.

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

Создайте класс MyNamespace.Информация, которая имеет конструктор thirdPartyNamespace.Информация:

 class MyInfo
{
    public DateTime StartTime {get; set;}
    public DateTime EndTime {get; set;}
    ... // other info properties you actually plan to use

    // Constructors:
    public MyInfo() { } // default constructor
    public MyInfo(ThirdParyNameSpace.Info info)
    {
        this.StartTime = info.StartTime.ToDateTime();
        this.EndTime = info.EndTime.ToDateTime();
        ...
    }
}
  

Вы видели, как легко добавить поддержку информации от четвертой стороны? Или насколько мало изменений произойдет, если изменится информация третьей стороны, или если вам нужно больше свойств (или меньше)?

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