Подтвердить что ты не робот

Принудительное использование плавающей запятой в .NET?

Я много читал о детерминизме с плавающей запятой в .NET, то есть гарантирую, что тот же код с теми же входами даст одинаковые результаты на разных машинах. Поскольку .NET не имеет таких параметров, как Java fpstrict и MSVC fp: strict, похоже, что существует консенсус, что нет никакой возможности обойти эту проблему, используя чистый управляемый код. Игра С# AI Wars решила использовать математику с фиксированной точкой, но это громоздкое решение.

Основная проблема заключается в том, что CLR позволяет промежуточным результатам жить в регистрах FPU, которые имеют более высокую точность, чем собственная точность типа, что приводит к непредсказуемо более высоким результатам точности. Статья MSDN инженера CLR Дэвида Нотарио объясняет следующее:

Обратите внимание, что с текущей спецификацией его выбор по-прежнему зависит от языка "Предсказуемость. Язык может вставлять conv.r4 или conv.r8 инструкции после каждой операции FP, чтобы получить" предсказуемое поведение".Очевидно, что это действительно дорого, а на разных языках разные компромиссы. С#, например, ничего не делает, если вы хотите сужение, вам придется вручную вставлять (float) и (double) броски.

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

Другие комментарии однако предполагают, что это не так просто. Эрик Липперт недавно заявил (внимание мое):

в некоторой версии среды выполнения, приведение в float явно дает другой результат, чем не делать этого. Когда вы явно бросаете float, компилятор С# дает подсказку для среды выполнения, чтобы сказать: "Возьмите эту вещь из режима сверхвысокой точности, если вы используете этот оптимизация".

Что это за "намек" на время выполнения? Указывает ли спецификация С#, что явное приведение в float вызывает вложение conv.r4 в IL? Определяет ли спецификация CLR, что команда conv.r4 приводит к сужению значения до его собственного размера? Только если оба они являются истинными, мы можем полагаться на явные приведения, чтобы обеспечить "предсказуемость" с плавающей запятой, как объяснил Дэвид Нотарио.

Наконец, даже если мы действительно можем принудить все промежуточные результаты к размеру собственного типа, достаточно ли этого, чтобы гарантировать воспроизводимость на разных машинах или существуют другие факторы, такие как настройки времени выполнения FPU/SSE?

4b9b3361

Ответ 1

Что это за "намек" на время выполнения?

Как вы предполагаете, компилятор отслеживает, действительно ли преобразование в double или float присутствует в исходном коде, и если это так, он всегда вставляет соответствующий код conv opcode.

Указывает ли спецификация С#, что явное приведение в float вызывает вложение идентификатора conv.r4 в IL?

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

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

Указывает ли спецификация CLR, что команда conv.r4 приводит к сужению значения до его собственного размера?

Да, в Разделе I, раздел 12.1.3, который я отмечаю , вы могли бы поискать себя, а не просить интернет сделать это за вас. Эти спецификации бесплатны в Интернете.

Вопрос, который вы не спрашивали, но, вероятно, должен иметь:

Есть ли какая-либо операция, отличная от литья, которая усекает, выплывает из режима высокой точности?

Да. Присвоение статическому полю возвращает поле экземпляра или элемент массива double[] или float[].

Является ли последовательное усечение достаточным для обеспечения воспроизводимости на машинах?

Нет. Я рекомендую вам прочитать раздел 12.1.3, в котором есть много интересного сказать о денонсациях и NaNs.

И, наконец, еще один вопрос, который вы не спрашивали, но, вероятно, должен иметь:

Как я могу гарантировать воспроизводимую арифметику?

Используйте целые числа.

Ответ 2

Модель чипа с плавающей точкой 8087 была ошибкой Intel на миллиард долларов. Идея выглядит хорошо на бумаге, дайте ей 8-разрядный стек, который хранит значения в расширенной точности, 80 бит. Чтобы вы могли писать вычисления, у промежуточных значений которых меньше шансов потерять значимые цифры.

Невозможно оптимизировать зверя. Хранение значения из стека FPU обратно в память дорого. Поэтому держать их внутри FPU является сильной целью оптимизации. Неизбежно, имея только 8 регистров, потребует обратной записи, если расчет достаточно глубок. Он также реализован в виде стека, а не свободно адресуемых регистров, что требует также гимнастики, которая может привести к обратному обращению. Неизбежно обратная запись урезает значение с 80 бит до 64 бит, теряя точность.

Таким образом, последствия не оптимизированного кода не приводят к тому же результату, что и оптимизированный код. И небольшие изменения в расчете могут иметь большое влияние на результат, когда промежуточные значения заканчиваются, и их нужно записать обратно. Параметр /fp: strict - это взломать это, он заставляет генератор кода испускать обратную запись, чтобы поддерживать согласованные значения, но с неизбежной и значительной потерей производительности.

Это полная скала и трудное место. Для джиттера x86 они просто не пытались решить проблему.

Intel не допустила такой же ошибки при разработке набора инструкций SSE. Регистры XMM свободно адресуются и не хранят лишние биты. Если вам нужны последовательные результаты, то компиляция с целью AnyCPU и 64-разрядной операционной системой является быстрым решением. Джиттер x64 использует SSE вместо инструкций FPU для математики с плавающей запятой. Хотя это добавило третий способ, что расчет может привести к другому результату. Если расчет неверен, потому что он теряет слишком много значимых цифр, то он будет постоянно ошибочным. На самом деле это немного бромид, но, как правило, только в том, что касается программиста.