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

Результат синуса зависит от используемого компилятора С++

Я использую два следующих компилятора С++:

  • cl.exe: Microsoft (R) C/С++ Оптимизация компилятора Версия 19.00.24210 для x86
  • g++: g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010

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

printf("%f\n", sin(5451939907183506432.0));

Результат с cl.exe:

0.528463

Результат с g++:

0.522491

Я знаю, что результат g++ более точен и что я могу использовать дополнительную библиотеку для получения этого же результата, но это не моя точка здесь. Я бы действительно понял, что здесь происходит: Почему cl.exe неправильно?

Забавно, если я применил по модулю (2 * pi) по параметру, то получим тот же результат, что и g++...

[EDIT] Просто потому, что мой пример выглядит сумасшедшим для некоторых из вас: это часть генератора псевдослучайных чисел. Не важно знать, является ли результат синуса точным или нет: нам просто нужно дать какой-то результат.

4b9b3361

Ответ 1

Я думаю, что комментарий Сэма ближе всего к знаку. Принимая во внимание, что вы используете недавнюю версию GCC/glibc, которая реализует функцию sin() в программном обеспечении (рассчитанную во время компиляции для соответствующего литерала), cl.exe для x86, вероятно, использует инструкцию fsin. Последнее может быть очень неточным, как описано в блоге "Случайный ASCII-блог", " Intel недооценивает границы ошибок на 1,3 квинтиллиона".

В частности, проблема с вашим примером заключается в том, что Intel использует неточную аппроксимацию pi при уменьшении диапазона:

При уменьшении диапазона от двойной точности (53-битная мантисса) pi результаты будут иметь около 13 бит точности (66 минус 53), для ошибки до 2 ^ 40 ULP (53 минус 13).

Ответ 2

У вас есть 19-разрядный литерал, но у двойника обычно есть точность 15-17 цифр. В результате вы можете получить небольшую относительную ошибку (при преобразовании в double), но достаточно большую (в контексте вычисления синуса) абсолютную ошибку.

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

std::cout << std::fixed << 5451939907183506432.0;

g++ результат будет 5451939907183506432.000000
cl будет 5451939907183506400.000000

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

Кроме того, рассмотрим этот код:

double a[1000];
for (int i = 0; i < 1000; ++i) {
    a[i] = sin(5451939907183506432.0);
}
double d = sin(5451939907183506432.0);
cout << a[500] << endl;
cout << d << endl; 

При выполнении с моим компилятором x86 VС++ вывод:

0.522491
0.528463

Похоже, что при заполнении массива sin скомпилируется вызов __vdecl_sin2, а когда есть одна операция, он скомпилируется вызовом __libm_sse2_sin_precise/fp:precise).

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

Ответ 3

Согласно cppreference:

Результат может иметь мало или вообще не иметь значения, если величина arg велика (до С++ 11)

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