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位在堆栈的内存中传递了doublefloat ,则可以通过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舍入模式,但这会改变结果,但是效果不是很好!)
也可以看看:

  • Intermediate Floating-Point Precision和Bruce Dawson的其余优秀系列有关浮点的文章。 (table of contents
    链接的文章介绍了VC++的CRT运行时启动的某些版本如何将x87 FP寄存器精度设置为53位尾数,而不是80位全精度。而且D3D9会将其设置为24,因此,如果使用x87完成操作,则即使double也仅具有float的精度。
  • https://en.wikipedia.org/wiki/Rounding#Table-maker.27s_dilemma
  • What Every Computer Scientist Should Know About Floating-Point Arithmetic
  • 10-08 08:43