Если два языка следуют IEEE 754, будут ли вычисления на обоих языках приводить к одинаковым ответам?
Я нахожусь в процессе преобразования программы из Scilab-кода в C++. Один цикл, в частности, дает несколько иной результат, чем исходный код Scilab (это длинный кусок кода, поэтому я не буду включать его в вопрос, но я постараюсь подвести итог ниже).
Проблема в том, что каждый шаг цикла использует вычисления из предыдущего шага. Кроме того, разница между вычислениями становится очевидной только около 100 000-й итерации (из примерно 300 000).
Примечание: я сравниваю выходные данные моей программы C++ с выходными данными Scilab 5.5.2, используя команду" format(25);". То есть я сравниваю 25 значащих цифр. Я также хотел бы отметить, что я понимаю, как точность не может быть гарантирована после определенного количества битов, но прочитайте разделы ниже, прежде чем комментировать. До сих пор все вычисления были идентичны до 25 цифр между двумя языками.
В попытках добраться до сути этого вопрос, до сих пор я пытался:
- анализ используемого типа данных:
Мне удалось подтвердить, что Scilab использует двойники IEEE 754 (согласно языковой документации). Кроме того, согласно Википедии, C++ не требуется для использования IEEE 754 для двойников, но из того, что я могу сказать, везде, где я использую двойник в C++, он идеально соответствует результатам Scilab.
- изучение использования трансцендентного функции:
Я также прочитал из , что каждый компьютерный ученый должен знать об арифметике с плавающей запятой, что IEEE не требует точного округления трансцендентных функций. Имея это в виду, я сравнил результаты этих функций (sin (), cos (), exp ()) в обоих языках и снова результаты оказались одинаковыми (до 25 цифр).
- использование других функций и предопределенных значений:
Я повторил выше приведены шаги для использования sqrt () и pow (). А также Значение Pi (я использую M_PI в C++ и %pi в Scilab). И снова результат был тот же.
- наконец, я переписал цикл (очень тщательно), чтобы убедиться, что код идентичен между двумя языками.
Примечание: интересно, что я заметил, что для всех приведенных выше вычислений результаты между двумя языками совпадают дальше, чем фактический результат вычислений (вне арифметики с плавающей запятой). Например:
Значение sin (x) при использовании Wolfram Alpha = 0.123456789.....
Значение sin (x) с помощью Scilab & C++ = 0.12345 yyyyy.....
где даже однажды значение, вычисленное с помощью Scilab или C++, начало отличаться от фактического результата (от Wolfram). Результаты каждого языка все еще соответствовали друг другу. Это приводит меня к мысли, что большинство значений вычисляются (между двумя языками) в одном и том же путь. Даже несмотря на то, что IEEE 754 этого не требует.
Мое оригинальное мышление было одним из первых трех пунктов выше, реализованных по-разному между двумя языками. Но из того, что я могу сказать, все, кажется, приводит к одинаковым результатам.
Возможно ли, что даже при том, что все входы в эти циклы идентичны, результаты могут быть разными? Возможно, потому, что происходит очень маленькая ошибка (мимо того, что я могу видеть с 25 цифрами), которая накапливается в течение время? Если да, то как я могу решить эту проблему?
4 ответа:
Нет, формат системы счисления не гарантирует эквивалентных ответов от функций на разных языках.
Функции, такие как
sin(x)
, могут быть реализованы различными способами, используя один и тот же язык (а также разные языки). Отличный пример-функцияsin(x)
. Многие реализации будут использовать таблицу поиска или таблицу поиска с интерполяцией. Это имеет преимущества скорости. Однако некоторые реализации могут использовать ряд Тейлора для оценки функции. Некоторые реализации могут использовать многочлены, чтобы получить близкое приближение.Наличие одного и того же числового формата является одним из препятствий для решения между языками. Другое дело-реализация функций.
Помните, что вам также нужно рассмотреть платформу. Программа, использующая 80-битный процессор с плавающей запятой, будет иметь другие результаты, чем программа, использующая 64-битную программную реализацию с плавающей запятой.
Некоторые архитектуры предоставляют возможность использования расширенных прецизионных регистров с плавающей запятой (например, 80 бит внутри, по сравнению с 64-битными значениями в ОЗУ). Таким образом, можно получить несколько разные результаты для одного и того же вычисления, в зависимости от структуры вычислений и уровня оптимизации, используемого для компиляции кода.
Да, можно получить и другие результаты. Это возможно, даже если вы используете один и тот же исходный код на одном и том же языке программирования для одной и той же платформы. Иногда достаточно иметь другой переключатель компилятора; например,
-ffastmath
приведет компилятор к оптимизации вашего кода для скорости, а не точности, и, если ваша вычислительная проблема не хорошо обусловлена, результат может быть значительно другим.Например, предположим, что у вас есть этот код:
Один из способов вычислить это-выполнить 7 умножения. Это будет поведение по умолчанию для большинства компиляторов. Однако вы можете ускорить это, указав параметр компилятораx_8th = x*x*x*x*x*x*x*x;
-ffastmath
, и полученный код будет иметь только 3 умножения:Результат будет немного отличаться, потому что арифметика конечной точности не ассоциативна, но достаточно близка для большинства приложений и намного быстрее. Однако, если ваши вычисления не являются хорошо обусловленная эта маленькая ошибка может быстро усилиться в большую.temp1 = x*x; temp2 = temp1*temp1; x_8th = temp2*temp2;
Обратите внимание, что возможно, что Scilab и C++ не используют одну и ту же последовательность команд, или что один использует FPU, а другой-SSE, поэтому не может быть способа заставить их быть точно такими же.
Как прокомментировал IInspectable, если ваш компилятор имеет _control87() или что-то подобное, вы можете использовать его для изменения параметров точности и/или округления. Вы можете попробовать комбинации этого, чтобы увидеть, если это имеет какой-либо эффект, но опять же, даже вам удается получить идентичные настройки для Scilab и C++ проблема может заключаться в различиях в фактических последовательностях команд.
Http://msdn.microsoft.com/en-us/library/e9b52ceh.aspx
Если используется SSE, я не уверен, что можно настроить, поскольку я не думаю, что SSE имеет 80-битный режим точности.
В случае использования FPU в 32-битном режиме, и если ваш компилятор не имеет что-то вроде _control87, вы можете использовать ассемблерный код. Если встроенная сборка не разрешена, необходимо вызвать функцию сборки. Этот пример взят из старой тестовой программы:
static short fcw; /* 16 bit floating point control word */ /* ... */ /* set precision control to extended precision */ __asm{ fnstcw fcw or fcw,0300h fldcw fcw }