Каким детерминированным является неточность с плавающей запятой?

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

  • Время между вычислениями?
  • Текущее состояние процессора?
  • Различные аппаратные средства
  • Язык/платформа/ОС?
  • Солнечные вспышки?

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

В настоящее время я работаю в Silverlight, хотя было бы интересно узнать, можно ли вообще ответить на этот вопрос.

Обновление:. Исходные ответы указывают да, но, видимо, это не совсем ясно, как обсуждалось в комментариях к выбранному ответу. Похоже, мне нужно будет сделать некоторые тесты и посмотреть, что произойдет.

Ответ 1

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

Определенные ошибки, о которых я знаю:

1.) некоторые операционные системы позволяют вам установить режим процессора с плавающей запятой способами, которые нарушают совместимость.

2.) промежуточные результаты с плавающей запятой часто используют точность 80 бит в регистре, но только 64 бит в памяти. Если программа перекомпилирована таким образом, что изменяется разброс значений в пределах функции, она может возвращать разные результаты по сравнению с другими версиями. Большинство платформ даст вам возможность заставить все результаты быть усеченными с точностью до памяти.

3.) Стандартные функции библиотеки могут изменяться между версиями. Я понимаю, что в gcc 3 vs 4 есть некоторые необычные примеры.

4.) Сам IEEE позволяет некоторым двоичным представлениям различаться... в частности значения NaN, но я не могу вспомнить детали.

Ответ 2

Короткий ответ заключается в том, что вычисления FP полностью детерминированы, согласно стандарт плавающей точки IEEE, но это не значит, что они полностью воспроизводимый по машинам, компиляторам, ОС и т.д.

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

Чтобы кратко ответить на ваши пункты:

  • Время между вычислениями и состоянием процессора имеют мало общего с это.

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

  • Язык, платформа и ОС также могут влияют на вещи. Для лучшего описания этого, чем я могу предложить, см. Ответ Джейсона Уоткинса. Если вы используете Java, посмотрите на Kahan rant на недостатки с плавающей запятой Java.

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

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

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

Ответ 3

Я думаю, ваше замешательство заключается в типе неточности вокруг плавающей запятой. В большинстве языков реализовано стандарт с плавающей точкой IEEE В этом стандарте описывается, как отдельные биты внутри float/double используются для создания числа. Обычно float состоит из четырех байтов и двухбайтовых байтов.

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

Неточность исходит из точности. Рассмотрим int vs float. Оба они обычно занимают одинаковое количество байтов (4). Тем не менее максимальное значение, которое может хранить каждый номер, сильно отличается.

  • int: примерно 2 млрд.
  • float: 3.40282347E38 (довольно немного больше)

Разница в середине. int, может представлять каждое число от 0 до примерно 2 миллиардов. Поплавок, однако, не может. Он может представлять 2 миллиарда значений от 0 до 3.40282347E38. Но это оставляет целый ряд ценностей, которые невозможно представить. Если математическое уравнение попадает в одно из этих значений, оно должно быть округлено до представляемого значения и, следовательно, считается "неточным". Ваше неверное определение может отличаться:).

Ответ 4

Кроме того, в то время как Goldberg является отличной ссылкой, исходный текст также неверен: IEEE754 не является gaurenteed портативный. Я не могу подчеркнуть это достаточно, учитывая, как часто это утверждение делается на основе сглаживания текста. Более поздние версии документа включают раздел, в котором подробно обсуждается:

Многие программисты могут не понимать, что даже программа, которая использует только числовые форматы и операции, предписанные стандартом IEEE, может вычислять разные результаты в разных системах. Фактически, авторы стандарта предполагали, что разные реализации могут получать разные результаты.

Ответ 5

Извините, но я не могу не думать о том, что всем не хватает смысла.

Если неточность важна для того, что вы делаете, вам следует искать другой алгоритм.

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

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

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

Ответ 6

Этот ответ в FAQ на С++, вероятно, описывает его наилучшим образом:

http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

Не только то, что разные архитектуры или компилятор может дать вам проблемы, числа указателей с плавающей запятой уже ведут себя странно в одной и той же программе. Как часто задают вопросы, если y == x истинно, это может означать, что cos(y) == cos(x) будет ложным. Это связано с тем, что процессор x86 вычисляет значение с 80 бит, а значение хранится как 64 бит в памяти, поэтому вы сравниваете усеченное 64-битное значение с полным 80-битным значением.

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

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

Ответ 7

Поскольку ваш вопрос отмечен С#, стоит подчеркнуть проблемы, с которыми столкнулся .NET:

  • Математика с плавающей запятой не ассоциативна, то есть (a + b) + c не гарантируется равным a + (b + c);
  • Различные компиляторы будут оптимизировать ваш код по-разному, и это может включать в себя переориентирование арифметических операций.
  • В .NET компилятор CLR JIT скомпилирует ваш код "на лету", поэтому компиляция зависит от версии .NET на машине во время выполнения.

Это означает, что вы не должны полагаться на свое приложение .NET, создавая те же результаты вычисления с плавающей точкой при запуске в разных версиях .NET CLR.

Например, в вашем случае, если вы записываете начальное состояние и ввод в симуляцию, затем установите пакет обновления, который обновляет CLR, ваша симуляция не может воспроизводиться одинаково при следующем запуске.

См. сообщение в блоге Shawn Hargreaves Является математическим детерминистом с плавающей запятой? для дальнейшего обсуждения, относящегося к .NET.

Ответ 8

HM. Поскольку ОП запросил С#:

Является ли децинитарист байт-кода С# CIT или генерирует другой код между разными прогонами? Я не знаю, но я не стал бы доверять Джиту.

Я мог бы подумать о сценариях, в которых JIT имеет некоторые функции обслуживания и решает потратить меньше времени на оптимизацию, потому что процессор делает тяжелое число хрустом где-то в другом месте (думаю, кодирование DVD с фоном)? Это может привести к незначительным различиям, которые могут впоследствии привести к огромным различиям.

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

Ответ 9

Это не полный ответ на ваш вопрос, но вот пример, демонстрирующий, что двойные вычисления в С# не являются детерминированными. Я не знаю, почему, но, казалось бы, несвязанный код, по-видимому, может повлиять на результат двойного вычисления ниже по течению.

  • Создайте новое приложение WPF в Visual Studio версии 12.0.40629.00 Update 5 и примите все параметры по умолчанию.
  • Замените содержимое MainWindow.xaml.cs следующим образом:

    using System;
    using System.Windows;
    
    namespace WpfApplication1
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                Content = FooConverter.Convert(new Point(950, 500), new Point(850, 500));
            }
        }
    
        public static class FooConverter
        {
            public static string Convert(Point curIPJos, Point oppIJPos)
            {
                var ij = " Insulated Joint";
                var deltaX = oppIJPos.X - curIPJos.X;
                var deltaY = oppIJPos.Y - curIPJos.Y;
                var teta = Math.Atan2(deltaY, deltaX);
                string result;
                if (-Math.PI / 4 <= teta && teta <= Math.PI / 4)
                    result = "Left" + ij;
                else if (Math.PI / 4 < teta && teta <= Math.PI * 3 / 4)
                    result = "Top" + ij;
                else if (Math.PI * 3 / 4 < teta && teta <= Math.PI || -Math.PI <= teta && teta <= -Math.PI * 3 / 4)
                    result = "Right" + ij;
                else
                    result = "Bottom" + ij;
                return result;
            }
        }
    }
    
  • Установите конфигурацию сборки в "Release" и создайте, но не запустите в Visual Studio.

  • Дважды щелкните встроенный exe, чтобы запустить его.
  • Обратите внимание, что в окне отображается "Нижнее изолированное соединение".
  • Теперь добавьте эту строку непосредственно перед "строковым результатом":

    string debug = teta.ToString();
    
  • Повторите шаги 3 и 4.

  • Обратите внимание, что в окне отображается "Правильное изолированное соединение".

Это поведение было подтверждено на компьютере коллеги. Обратите внимание, что в окне последовательно отображается "Right Insulated Joint", если выполнено одно из следующих утверждений: exe запускается из Visual Studio, exe был создан с использованием конфигурации Debug, или "Предпочтение 32-бит" не отмечено в свойствах проекта.

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

Ответ 10

Очень мало FPU соответствуют стандарту IEEE (несмотря на их требования). Таким образом, запуск одной и той же программы на другом оборудовании действительно даст вам разные результаты. Результаты, скорее всего, будут в угловых случаях, которые вы уже должны избегать как часть использования FPU в вашем программном обеспечении.

Ошибки IEEE часто исправляются в программном обеспечении, и вы уверены, что операционная система, в которой вы работаете сегодня, включает в себя правильные ловушки и исправления от производителя? Что до или после обновления ОС? Удалены ли все ошибки и исправлены ошибки? Является ли компилятор C синхронизированным со всем этим и является компилятором C, создающим правильный код?

Тестирование может оказаться бесполезным. Вы не увидите проблему до тех пор, пока не поставите продукт.

Соблюдать правило FP № 1: Никогда не используйте сравнение if (something == something). И правило номер два ИМО будет связано с ascii с fp или fp на ascii (printf, scanf и т.д.). Там больше ошибок и ошибок, чем в аппаратном обеспечении.

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

Поглощая огромное количество логики, fpu, вероятно, будет очень быстрым (один такт). Не медленнее, чем целое число. Не путайте это с тем, чтобы современный fpus был таким же простым, как alus, fpus дороги. (alus также потребляет больше логики для умножения и деления, чтобы получить это до одного тактового цикла, но его не так сильно, как fpu).

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