#include <cmath>
#include <cstdio>
int main() {
float a = std::asin(-1.f);
printf("%.10f\n", a);
return 0;
}
我使用clang,g ++和Visual Studio在多个平台上运行了以上代码。他们都给了我相同的答案:-1.5707963705
如果我使用clang在macOS上运行它,它将得到-1.5707962513
。
macOS上的Clang应该使用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
答案 0 :(得分:4)
asin
在libm
中实现,它是标准C库的一部分,而不是C ++标准库的一部分。 (从技术上讲,C ++标准库包括C库函数,但实际上Gnu和LLVM C ++库实现都依赖于基础平台数学库。)这三个平台(Linux,OS X和Windows)各自具有自己的平台。数学库的实现,因此,如果使用库函数,则它肯定是一个不同的库函数,并且结果的最后一位位置(测试显示的结果)可能会有所不同。
但是,很可能在所有情况下都不会调用该库函数。这将取决于编译器以及传递给它们的优化选项(以及可能还有其他一些选项)。由于asin
函数是标准库的一部分,因此具有已知的行为,对于编译器来说,在编译时计算std::asin(-1.0F)
的值是完全合法的,就像其他常数表达式一样(像1.0 + 1.0
,几乎所有编译器都会在编译时将其常数折叠为2.0
。
由于您没有提及正在使用的优化设置,因此很难确切说明正在进行的操作,但是我对http://gcc.godbolt.org做了一些测试,以了解基本概念:
GCC 常量将调用折叠到asin
,而没有任何优化标志,但不会在printf
中预先计算参数提升(这会转换{{1} }传递给a
,以将其传递给double
),除非您至少指定了printf
。 (已通过GCC 8.3测试)。
-O1
,否则 Clang (7.0)会调用标准库函数。但是,如果您显式调用-O2
,它的常数将折叠为asinf
。走吧。
MSVC (v19.16)并非恒定折叠。根据优化设置,它要么调用-O1
包装器,要么直接调用std::asin
。我不太了解包装程序的功能,也没有花太多时间进行调查。
GCC和Clang常量均将表达式折叠为完全相同的二进制值(0xBFF921FB60000000为双精度),即二进制值-1.10010010000111111011011(尾随零被截断)。
请注意,三个平台上的asinf
实现之间也存在差异(printf
也是平台C库的一部分)。从理论上讲,您可能会从相同的二进制值看到不同的十进制输出,但是由于printf
的参数在调用printf
之前被提升为double
,并且提升是精确定义的,而不是改变价值,这在这种特殊情况下极不可能产生任何影响。
请注意,如果您确实关心第七位小数点,请使用printf
而不是double
。实际上,您只应在精度不重要的非常特定的应用程序中使用float
。正常的浮点类型为float
。
答案 1 :(得分:3)
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
函数的来源。