使用 glibc/libm 和 float32 的 atan2 的错误结果

Wrong result for atan2 with glibc / libm and float32

我目前正在为医疗设备开发固件,其中涉及很多困难的数学运算。目标处理器支持硬件中的浮点运算,但仅支持 float32(又名 single)。

为了模拟行为并证明我的公式和代码的正确性,我已将固件的相关/数学部分移植到 Linux 中的 GCC 工具链(gcc 6.3.0,libc6 2.24),仔细检查 float32 在任何地方都被使用,并且没有使用编译器开关,这可能会降低数学运算的精度或标准兼容性;值得注意的是,有 none 个 -ffast-math 或其朋友。

现在,对于一小组输入参数,我得到了意想不到的结果。我已经追踪到这个问题并得出结论,libm 为非常小的一组输入参数计算了 arctan(准确地说:atan2)的错误结果。

例如,如果我有

#include <math.h>

#define C_RAD2DEG (57.29577951308f)

int main(void)
{
  float f_Temp = C_RAD2DEG * atan2f(0.713114202f, 0.665558934f);
}

f_Temp 被计算为 46.9755516f,其中正确的结果将是 46.975548972f.

请注意,我通常了解不同浮点数据类型、舍入错误等问题。

然而,我的感觉是,即使考虑到 float32 的低精度,上面显示的误差也高了一个数量级,不幸的是,对于随后的计算,该误差太大了。

此外,atan2 函数的可能输入参数中只有很小一部分受到此问题的影响。

任何人都可以快速解释一下这是 libm 中的错误,还是仅仅是由于 float32 的不精确以及计算 [=17= 所需的大量顺序操作]?

您作为观察结果报告的数字 46.9755516f 对应于 float 值 46.975551605224609375。

您作为预期结果报告的数字 46.975548972f 对应于 float 值 46.97554779052734375。

这些是相邻的 float 值,这意味着它们相差 1 个最小精度单位 (ULP)。 (它们的差异是 3.814697265625e-06,这是当最高有效位的值为 32 时 float 有效位中最低有效位的值,就像 47 左右的数字一样。)这是最小的可能数量float 可以按该比例变化。

通常,数学库例程很难实现,并且没有人用正确的舍入(舍入到最接近精确数学值的可表示数字)和已知的有界 运行 时间来实现所有这些例程。一些 ULP 错误在三角函数例程中并不罕见。

即使您使用的 libc 代码提供了正确的舍入结果,将其从弧度转换为度数也会引入两个舍入错误(将 180/π 转换为可表示的值并乘以它)。期望最终结果是最接近理想数学结果的 float 是不合理的;你应该期待几个 ULP 错误。