Приведение результата к плавающей точке в методе, возвращающем результат изменений с плавающей точкой - PullRequest
53 голосов
/ 10 января 2012

Почему этот код печатает False в .NET 4?Кажется, что неожиданное поведение вызвано явным приведением.

Я бы хотел получить ответ за пределы "с плавающей запятой неточно" или "не делайте этого".

float a(float x, float y)
{
  return ( x * y );
}

float b(float x, float y)
{
  return (float)( x * y );
}

void Main()
{
  Console.WriteLine( a( 10f, 1f/10f ) == b( 10f, 1f/10f ) );
}

PS: Этот код пришел из модульного теста, а не кода выпуска.Код был написан так сознательно.Я подозревал, что со временем это потерпит неудачу, но я хотел точно знать, когда и почему.Ответ доказывает правильность этой техники, поскольку она обеспечивает понимание, выходящее за рамки обычного понимания детерминизма с плавающей запятой.И это было целью написания этого кода таким образом;преднамеренное исследование.

PPS: модульное тестирование проходило в .NET 3.5, но теперь не проходит после обновления до .NET 4.

Ответы [ 2 ]

82 голосов
/ 10 января 2012

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

Спецификация C # предельно ясна по этому вопросу:


Операции с плавающей запятой могут выполняться с большей точностью, чем тип результата операции. Например, некоторые аппаратные архитектуры поддерживают «расширенный» или «длинный двойной» тип с плавающей точкой с большей дальностью и точностью, чем тип double, и неявно выполняют все операции с плавающей точкой, используя этот тип с более высокой точностью. Только при чрезмерных затратах на производительность такие аппаратные архитектуры могут быть выполнены для выполнения операций с плавающей запятой с меньшей точностью, и вместо того, чтобы требовать реализации для потери как производительности, так и точности, C # позволяет использовать тип с более высокой точностью для всех операций с плавающей запятой , Помимо предоставления более точных результатов, это редко дает ощутимые результаты. Однако в выражениях вида x * y / z, где умножение дает результат, выходящий за пределы двойного диапазона, а последующее деление возвращает временный результат обратно в двойной диапазон, тот факт, что выражение оценивается в формате более высокого диапазона может привести к конечному результату вместо бесконечности.


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

Если вам это не нравится, не используйте двоичные числа с плавающей запятой; либо используйте десятичные числа или рациональные рациональные числа.

Я не понимаю, почему приведение к float в методе, который возвращает float, имеет значение, которое он делает

Отличный балл.

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

То, что это происходит с округлением до "правильного ответа", является просто счастливой случайностью; правильный ответ получен, потому что в этом случае потеря точности потеряла его в правильном направлении .

Чем отличается .net 4?

Вы спрашиваете, в чем разница между 3,5 и 4,0 средами выполнения; Различие очевидно, что в 4.0 джиттер выбирает более высокую точность в вашем конкретном случае, а джиттер 3,5 выбирает не делать этого. Это не значит, что эта ситуация была невозможна в 3,5; это было возможно в каждой версии среды исполнения и каждой версии компилятора C #. Вы только что натолкнулись на случай, когда на вашей машине они различаются по своим деталям. Но для джиттера всегда было разрешено выполнять эту оптимизацию, и он всегда делал это по своей прихоти.

Компилятор C # также полностью вправе выбирать аналогичную оптимизацию при вычислении констант с плавающей точкой во время компиляции. Два по-видимому идентичных вычисления в константах могут иметь различные результаты в зависимости от деталей состояния времени выполнения компилятора.

* +1045 * МоВ общем, ваше ожидание, что числа с плавающей запятой должны иметь алгебраические свойства действительных чисел, полностью не соответствует действительности; у них нет этих алгебраических свойств. Операции с плавающей запятой даже не ассоциативны ; они, конечно, не подчиняются законам мультипликативных инверсий, как вы, вероятно, ожидаете их. Числа с плавающей точкой являются лишь приближением реальной арифметики; приближение, которое достаточно близко, скажем, для моделирования физической системы или вычисления сводной статистики, или чего-то подобного.
0 голосов
/ 28 апреля 2013

У меня сейчас нет компилятора Microsoft, и Mono не имеет такого эффекта.Насколько я знаю, GCC 4.3+ использует gmp и mpfr для вычисления некоторых вещей во время компиляции .Компилятор C # может делать то же самое для не виртуальных, статических или частных методов в одной сборке.Явное приведение может помешать такой оптимизации (но я не вижу причин, почему оно не может иметь такое же поведение).Т.е. он может соответствовать вычислению константного выражения до некоторого уровня (для b() это может быть, например, вплоть до приведения).

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...