我在 CI 期间注意到这段代码
#include <stdio.h>
#include <math.h>
int main()
{
const double d = 3.81219767052986080458;
fprintf(stderr, "%.20f\n", sin(d));
return 0;
}
编译为
gcc test.c -lm -o test
我想知道什么会影响它?自由?海湾合作委员会?可能是CPU?
这种细微的差异可能会在紧密循环的集成过程中累积。
有没有一种简单的方法可以摆脱它而不影响性能?例如,gcc 编译器参数?
我想知道什么会影响它?自由?
相同输入的
sin
的差异通常是由标准 C 库中 sin
的不同实现引起的。 sin
的实现可能位于名为 libm
的“数学库”中,也可能位于其他文件中,具体取决于平台。
(另一种可能性是输入数字
3.81219767052986080458
在不同的编译器版本中被不同地解析,但这种可能性较小。)
理想情况下,
sin
实现返回等于其输入操作数的数学正弦值的数字,四舍五入到可以浮点格式表示的最接近的值。1这将产生您显示的第一个结果,或者更准确地说, - .62146009873891927544065083566238172352313995361328125。然而,C 标准并不要求这样做;它允许实现产生未正确舍入的结果。
对于某些数学库函数,人类还不知道如何在已知界限的执行时间内以正确的舍入方式计算整个函数。我们可以使用扩展精度算术将函数近似到任何所需的精度,并且我们可以编写迭代算法来计算越来越高的精度,直到我们准确地知道结果,以至于我们知道应该采用哪种舍入方式。然而,某些函数的某些结果可能非常接近舍入变化的点(可表示值之间的中间),因此需要很高的精度。我们还不知道所有函数需要多少精度。因此,我们可以编写一个在给定足够时间的情况下工作的迭代算法,但是,对于某些函数,我们不知道需要多少时间来覆盖所有情况。
对于 IEEE-754 二进制 32 和二进制 64 格式(通常用于
float
和 double
)的正弦,情况并非如此。所有最坏的情况都已被完全绘制出来。因此,可以实现正确舍入的正弦,并且可以以合理的速度完成,尽管有些人更喜欢未正确舍入的更快实现。
CORE-MATH 项目正在致力于进一步开发正确舍入的实现。
尽管如此,3.81219767052986080458 中的差异还是有些令人惊讶。对于周期函数,通常的设计是将基区间外的参数减少到基区间,然后计算近似基区间内函数的多项式。对于正弦,实现通常至少使用一点额外的精度来减少基本间隔和多项式评估,并且一点额外的精度应该足以满足这种情况。我确实注意到,双最近的 3.81219767052986080458 的正弦距两个最近的可表示值之间的中点仅约 0.2%。所以八九个额外的位应该就足够了。但也许你的正弦实现之一没有做到这一点。
有没有一种简单的方法可以摆脱它而不影响性能?
如果问题出在
sin
实现中,您将不得不使用不同的数学库。它的性能可能会有所不同。
1 对于一般函数,四舍五入会打破关系,转而采用其有效数中具有偶数低位(以 2 为基数的浮点数的位)的最接近的可表示值。这与正弦无关,因为它唯一可表示的具有有理正弦的操作数为零,因此所有操作数都不涉及关系。