Плавающее преобразование в двойное - лучшее утверждение в модульном тесте?

Учитывая заявления

float f = 7.1f;
double d = f;

Что мы можем утверждать в модульном тесте о d?


Например, это не работает:

Console.WriteLine(d == 7.1d); // false
Console.WriteLine(d < 7.1d + float.Epsilon); // true by luck
Console.WriteLine(d > 7.1d - float.Epsilon); // false (less luck)

Лучший способ, который я нашел до сих пор, - это преобразовать значение обратно:

float f2 = (float)d;
Console.WriteLine(f2 == f); // true

Что было бы так же, как грубый способ сказать

Console.WriteLine(d == 7.1f); // 7.1f implicitly converted to double as above

Этот вопрос НЕ касается двойной точности и точности с плавающей запятой в целом, но на самом деле ТОЛЬКО о прагматическом вопросе о том, как модульный тест может лучше всего описать пределы d. В моем случае d является результатом преобразования, которое происходит в коде, сгенерированном с помощью генерации облегченного кода. При тестировании этой генерации кода я должен делать утверждения о результате этой функции, и это, наконец, сводится к простому вопросу выше.


person citykid    schedule 19.12.2012    source источник
comment
Какую среду модульного тестирования вы используете? Вы не обязательно можете утверждать против условия, которое вы получаете, например. Assert.IsTrue(condition), вы могли бы использовать, например. Assert.AreEqual(value1, value2), который может обрабатывать эквивалентность между числовыми форматами.   -  person StuperUser    schedule 19.12.2012
comment
Я все еще не уверен, что вы пытаетесь проверить здесь. Двойное число имеет большую точность, чем число с плавающей запятой, и поэтому они вряд ли когда-либо будут равны друг другу.   -  person CodeCaster    schedule 19.12.2012
comment
Если вы опустите последнюю букву f в константе, используемой для инициализации поплавка, это может быть еще хуже, см. созданный пример здесь stackoverflow.com/questions/13276862/   -  person aka.nice    schedule 19.12.2012


Ответы (3)


Ваш «лучший способ» утверждает, что ваш сгенерированный код возвращает что-то, что в пределах погрешности float составляет 7.1. Это может быть то, что вы хотите проверить, и в этом случае продолжайте.

С другой стороны, вы можете захотеть утверждать, что ваш сгенерированный код возвращает конкретно результат приведения 7.1f к double, и в этом случае вы могли бы сделать:

Console.WriteLine(d == (double)f);

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

Это действительно зависит от того, для чего вы будете использовать d. Если это случай, когда что-то пойдет не так, если это не точное значение, проверьте точное значение, но если это нормально, чтобы быть в пределах float значения, проверьте против float.

person Rawling    schedule 19.12.2012
comment
Роулинг, это все для меня. Я ожидаю, что мой код преобразует 7.1f в двойное, поэтому мое утверждение теперь d.Should().Be((double)7.1f); это делает мои ожидания наиболее ясными. Спасибо за ваш вклад. - person citykid; 19.12.2012

Чтобы сравнить два значения с плавающей запятой, ibm предлагает протестировать abs(a/b - 1) < epsilon

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

так что на самом деле вы должны проверить

Math.Abs(d/(double)f) - 1) < float.Epsilon)
person Dmitry Dovgopoly    schedule 19.12.2012
comment
спасибо за подсказку от IBM: если вы не знаете шкалу основных измерений, использование теста abs(a/b - 1) ‹ эпсилон, вероятно, будет более надежным, чем простое сравнение разницы. - person citykid; 19.12.2012
comment
+1. Да, нет смысла добавлять эпсилон к возможно большому числу. Это не имело бы никакого эффекта. Это будет работать только для чисел с фиксированной точкой. - person Olivier Jacot-Descombes; 19.12.2012
comment
Документ, на который вы указываете, опубликован IBM, но автором его является лицо и корпорация, отличные от IBM. Таким образом, неясно, делает ли это предложение IBM, так же как издательство книг не соглашается со всем, что утверждают авторы, которые они публикуют. И обвинять IBM в пропаганде этой небрежной практики на основании этого не совсем справедливо. - person Eric Postpischil; 19.12.2012
comment
@EricPostpischil, как именно это «неаккуратно»? Для меня это выглядит как довольно разумное приближение к тому, как проверить основной вопрос. - person RonLugge; 27.08.2013
comment
@RonLugge: Причины небрежности слишком многочисленны и сложны, чтобы их можно было подробно описать в комментарии. Достаточно сказать, что этот метод уменьшает «ложные» неравные результаты за счет увеличения ложных результатов, равных, значение epsilon обычно не должно быть Epsilon, к которому относится этот ответ, относительный порог не является правильным критерием для использования если ошибка возникает из-за значений, отличных от окончательного результата, этот тест не подходит для ситуации в этом вопросе, где может быть проверено точное равенство, и программное обеспечение, как правило, должно быть спроектировано таким образом, чтобы избежать необходимости в этих сравнениях. - person Eric Postpischil; 27.08.2013

(float) d == f.

Другой ответ предложил d == (double) f, но это бесполезный тест, потому что (double) f выполняет то же преобразование, что и d = f неявно. Таким образом, единственное, что может быть проверено этим утверждением, — это нарушение какого-то аспекта реализации (например, компилятор реализовал одно из преобразований неправильно и способом, отличным от другого), какой-то внешний механизм изменил d или f между присваиванием и утверждение или исходный код были нарушены, так что d не был ни double, ни float, ни каким-либо типом, который может точно содержать значение f, или присваивание d = f не было выполнено.

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

Вместо этого мы конвертируем из более широкого формата обратно в более узкий формат. Если d отличается от f, это преобразование может обнаружить ошибку. Например, предположим, что f содержит 0x1p-1000, но по какой-то причине это не представляется в формате d, поэтому оно было округлено до нуля. Затем (float) d == f преобразуется в (float) 0 == 0x1p-1000, затем в 0 == 0x1p-1000, затем в false. Кроме того, этот тест может обнаружить те же ошибки, что и другое предложение: неверная реализация, изменение d или f, неправильный тип d и отсутствующее назначение d = f.

Кроме этого, какие ошибки вы попытаетесь обнаружить с помощью утверждения здесь?

person Eric Postpischil    schedule 19.12.2012
comment
Стоит отметить, что преобразование из float в Decimal или из double в Decimal может привести к потерям даже для значений, которые точно представляются в обоих форматах. Например, преобразование 16777215f в Decimal дает значение 16777220, хотя float точно представляет значение 16 777 215, а Decimal также может содержать это значение. - person supercat; 21.12.2012
comment
@supercat: «… в каждой нормальной реализации преобразование… в более высокую точность одной и той же системы счисления не приводит к ошибкам…». - person Eric Postpischil; 21.12.2012
comment
Это правда, что нельзя ожидать, что каждое значение float будет представимо в Decimal, учитывая, что они используют разные системы счисления, и, конечно же, нельзя ожидать, что преобразование будет точным в случаях, когда формат назначения не имеет представления. для рассматриваемого значения. Мое намерение состояло не в том, чтобы противоречить вам, а в том, чтобы подчеркнуть, что если системы счисления различаются, даже значения, которые точно представляются в старом и новом форматах, могут быть преобразованы странно (возможно, задокументировано округление 16777215f до 16777220m, но это кажется по меньшей мере странным ). - person supercat; 22.12.2012
comment
@supercat: Если преобразование из float в Decimal дает значение, отличное от исходного float, даже если оно точно представлено в Decimal, то программное обеспечение, выполняющее преобразование, неисправно. - person Eric Postpischil; 27.08.2013
comment
Мой конкретный пример выше был дефектным, так как я использовал неправильный метод преобразования, и реальные проблемы не проявляются до тех пор, пока числа не станут больше, но происходит то, что преобразование из Double в Decimal предполагает, что значение double, например 12345678.900000000372529 ( которое было бы лучшим представлением значений между 12345678,89999999944120646 и 12345678,9000000013038516), скорее всего, предназначено для точного представления 12345678,9, чем 12345678,900000000372529. Однако процедура довольно небрежна, поскольку она округляет то, что явно должно быть релевантной точностью. - person supercat; 27.08.2013