Насколько детерминистична погрешность с плавающей запятой? - PullRequest
25 голосов
/ 30 ноября 2008

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

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

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

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

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

Ответы [ 10 ]

20 голосов
/ 30 ноября 2008

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

Конкретные ошибки, о которых я знаю:

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

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

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

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

17 голосов
/ 30 ноября 2008

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

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

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

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

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

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

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

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

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

8 голосов
/ 30 ноября 2008

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

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

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

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

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

4 голосов
/ 30 ноября 2008

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

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

2 голосов
/ 19 апреля 2011

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

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

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

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

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

2 голосов
/ 17 июня 2009

Этот ответ в C ++ 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-битной и наоборот, но будут работать нормально при запуске на одной и той же арке.

2 голосов
/ 30 ноября 2008

Извините, но я не могу не думать, что все упускают суть.

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

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

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

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

1 голос
/ 30 ноября 2008

HM. Поскольку ОП попросил C #:

Является ли байт-код C # JIT детерминированным или генерирует разный код для разных прогонов? Я не знаю, но я бы не стал доверять Jit.

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

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

0 голосов
/ 04 марта 2016

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

  1. Создайте новое приложение WPF в Visual Studio версии 12.0.40629.00 Обновление 5 и примите все параметры по умолчанию.
  2. Заменить содержимое 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;
            }
        }
    }
    
  3. Установите для конфигурации сборки значение "Release" и выполните сборку, но не запускайте ее в Visual Studio.

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

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

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

Такое поведение было подтверждено на машине коллеги. Обратите внимание, что в окне последовательно отображается «Правое изолированное соединение», если выполняется любое из следующих условий: исполняемый файл запускается из Visual Studio, исполняемый файл был создан с использованием конфигурации отладки, или «Предпочитать 32-разрядный» не отмечен в свойствах проекта.

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

0 голосов
/ 30 ноября 2008

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

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

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

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

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

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

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

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