acos(double)
在x64和x32 Visual Studio上给出不同的结果。
printf("%.30g\n", double(acosl(0.49990774364240564)));
printf("%.30g\n", acos(0.49990774364240564));
在x64上:
1.0473040763868076
在x32上:1.0473040763868078
在启用了sse的linux4.4 x32和x64上:
1.0473040763868078
有没有一种方法可以使VSx64
acos()
给出1.0473040763868078
作为结果? 最佳答案
TL:DR:这是正常现象,您无法合理地更改它。
32位库可能使用x87寄存器中的80位FP值作为临时值,以避免在每次操作后舍入到64位double
。 (除非有一个完整的单独的库,否则编译您自己的代码以使用SSE不会更改库中的内容,甚至不会更改将数据传递到库的调用约定。但是由于32位在堆栈的内存中传递了double
和float
,则可以通过SSE2或x87随意加载该库。但是,除非非SSE代码无法使用该库,否则您无法获得在xmm寄存器中传递FP值的性能优势。)
它们之所以不同,还可能仅仅是因为它们使用不同的操作顺序,从而沿途产生了不同的临时工。除非它们是分别用asm手写的,否则这似乎不太合理。如果它们是从相同的C源代码构建的(没有“不安全”的FP优化),则由于FP数学的这种非关联行为,不允许编译器重新排序。
glibc的libm(在Linux上使用)通常偏向于精度而不是速度,因此它为32位和64位的尾数的最后一位提供了正确舍入的结果。 IEEE FP标准仅要求将基本操作(+-* / FMA和FP余数)“正确舍入”到尾数的最后一位。 (即,舍入误差最多为0.5 ulp)。 (根据 calc
的确切结果是1.047304076386807714...
。请记住double
(在使用普通编译器的x86上)是IEEE754 binary64,因此内部的尾数和指数位于base2中。但是,如果您打印了足够的额外十进制数字,则可以知道...7714
应该四舍五入到...78
,尽管实际上您应该打印更多的数字,以防它们不为零以上。我只是假设它是...78000
。)
因此,Microsoft的64位库实现生成1.0473040763868076
,除了不使用之外,您几乎无能为力。 (例如,找到自己的acos()
实现并使用它。)但FP确定性很难,即使您将SSE限制为x86也是如此。参见Does any floating point-intensive code produce bit-exact results in any x86-based architecture?。如果将自己限制在一个编译器中,则可以避免使用复杂的库函数(例如acos()
)。
如果使用x87,并且更改x87精度设置会影响它,则您可能能够获得32位库版本以产生与64位版本相同的值。但是,另一种方式是不可能的:SSE2具有针对64位double
和32位float
的单独指令,并且总是在每条指令后舍入,因此您不能更改任何设置来提高精度结果。 (您可以更改SSE舍入模式,但这会改变结果,但是效果不是很好!)
也可以看看:
链接的文章介绍了VC++的CRT运行时启动的某些版本如何将x87 FP寄存器精度设置为53位尾数,而不是80位全精度。而且D3D9会将其设置为24,因此,如果使用x87完成操作,则即使
double
也仅具有float
的精度。