Результат синуса зависит от используемого компилятора С++
Я использую два следующих компилятора С++:
- 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] Просто потому, что мой пример выглядит сумасшедшим для некоторых из вас: это часть генератора псевдослучайных чисел. Не важно знать, является ли результат синуса точным или нет: нам просто нужно дать какой-то результат.
Ответы
Ответ 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
был невелик.