Как правильно проверять число с плавающей или двойной на качество и другие сравнения - PullRequest
0 голосов
/ 31 марта 2019

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

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

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

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

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

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

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

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

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

Любой совет приветствуется.

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

Ответы [ 2 ]

2 голосов
/ 31 марта 2019

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

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

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

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

0 голосов
/ 01 апреля 2019

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

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

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

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

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

Напишите две функции расширения класса 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 && 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. Поскольку тики являются целыми числами, у вас будет меньше проблем с конвертацией

Разделение интересов

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

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

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

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

Создайте класс MyNamespace.Info, у которого есть конструктор thirdPartyNamespace.Info:

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 от стороннего разработчика? Или насколько мало изменений, если информация сторонней организации меняется, или если вам нужно больше свойств (или меньше)?

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

...