asin выдает разные ответы на разных платформах, используя Clang - PullRequest
3 голосов
/ 19 марта 2019
#include <cmath>
#include <cstdio>

int main() {
    float a = std::asin(-1.f);
    printf("%.10f\n", a);
    return 0;
}

Я запустил приведенный выше код на нескольких платформах, используя clang, g ++ и Visual studio.Все они дали мне один и тот же ответ: -1.5707963705

Если я запустил его на macOS с использованием clang, это даст мне -1.5707962513.Предполагается, что Clang в macOS использует libc ++, но имеет ли macOS собственную реализацию libc ++?

Если я запускаю clang --verison, я получаю:

Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Target: x86_64-apple-darwin18.0.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

Ответы [ 2 ]

4 голосов
/ 20 марта 2019

asin реализован в libm, который является частью стандартной библиотеки C, а не стандартной библиотеки C ++.(Технически стандартная библиотека C ++ включает функции библиотеки C, но на практике реализации библиотек C ++ Gnu и LLVM полагаются на базовую математическую библиотеку платформы.) Три платформы - Linux, OS X и Windows - каждая имеет свою собственнуюреализация математической библиотеки, поэтому, если бы использовалась библиотечная функция, это, безусловно, могла бы быть другая библиотечная функция, и результат мог бы отличаться в последней битовой позиции (что показывает ваш тест).

Однако,вполне возможно, что библиотечная функция никогда не вызывается во всех случаях.Это будет зависеть от компиляторов и опций оптимизации, которые вы им передаете (и, возможно, некоторых других опций).Поскольку функция asin является частью стандартной библиотеки и, следовательно, имеет известное поведение, для компилятора вполне законно вычислять значение std::asin(-1.0F) во время компиляции, как это было бы для любого другого константного выражения (например, 1.0 + 1.0).(который почти любой компилятор будет постоянно сворачивать до 2.0 во время компиляции).

Поскольку вы не упоминаете, какие настройки оптимизации вы используете, трудно точно сказать, что происходит, но я сделалнесколько тестов с http://gcc.godbolt.org, чтобы получить основную идею:

  • GCC константа сворачивает вызов к asin без каких-либо флагов оптимизации, но это делаетне предварительно вычислять продвижение аргумента в printf (который преобразует a в double, чтобы передать его printf), если вы не укажете по крайней мере -O1.(Протестировано с GCC 8.3).

  • Clang (7.0) вызывает стандартную библиотечную функцию, если вы не укажете хотя бы -O2.Тем не менее, если вы явно вызываете asinf, он постоянно сгибается в -O1.Go figure.

  • MSVC (v19.16) не является постоянным сгибом.Он либо вызывает оболочку std::asin, либо напрямую вызывает asinf, в зависимости от настроек оптимизации.Я не совсем понимаю, что делает обертка, и я не тратил много времени на изучение.

И константа GCC, и Clang сворачивают выражение в одно и то же двоичное значение (0xBFF921FB60000000 какdouble), что является двоичным значением -1.10010010000111111011011 (конечные нули обрезаются).

Обратите внимание, что существует также разница между реализациями printf на трех платформах (printf также является частью платформыС библиотека).Теоретически, вы могли видеть различные десятичные выходные данные из того же двоичного значения, но, поскольку аргумент printf повышен до double до вызова printf, и повышение точно определено и не изменяет значение, этоКрайне маловероятно, что это окажет какое-либо влияние в данном конкретном случае.

В качестве дополнительного примечания: если вы действительно заботитесь о седьмой десятичной запятой, используйте double вместо float.В самом деле, вы должны использовать float только в очень специфических приложениях, в которых точность не важна;нормальный тип с плавающей запятой double.

3 голосов
/ 19 марта 2019

Математически точное значение asin(-1) будет -pi/2, что, конечно, иррационально и невозможно представить точно как float.Двоичные цифры pi/2 начинаются с

1.1001001000011111101101010100010001000010110100011000010001101..._2

Ваши первые три библиотеки округляют это (правильно) до

1.10010010000111111011011_2 = 1.57079637050628662109375_10

В MacOS, похоже, оно усекается до:

1.10010010000111111011010_2 = 1.57079625129699707031250_10

Это ошибка менее 1 ULP (единица на последнем месте).Это может быть вызвано либо другой реализацией, либо вашим FPU установлен другой режим округления, либо, возможно, в некоторых случаях компилятор вычисляет значение во время компиляции.

Я не думаю, что стандарт C ++действительно дает какие-либо гарантии точности трансцендентных функцийЕсли у вас есть код, который действительно зависит от точности (независимой от платформы / оборудования), я предлагаю использовать библиотеку, например, MPFR .В противном случае просто жить с разницей.Или взгляните на источник функции asin, которая вызывается в каждом случае.

...