我目前正在开发用于医疗设备的固件,其中涉及许多困难的数学操作。目标处理器支持硬件中的浮点操作,但仅float32
(又称single
)。
为了模拟行为并证明我的公式和代码的正确性,我将固件的相关/数学部分移植到Linux(GCC 6.3.0,libc6 2.24)中的GCC工具链,双重检查float32
IS到处使用,没有使用任何编译器开关,可以降低数学操作的精度或标准兼容性;值得注意的是,没有-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
的不精确和计算atan2
所需的大量顺序操作?
您报告的数字作为观察到的结果46.9755516f
,对应于float
值46.9755516052224609375。
您报告的数字作为预期结果46.975548972f
,对应于float
值46.97554779052734375。
这些是相邻的float
值,这意味着它们不同于精度最低的单位(ULP)。(它们的差异为3.814697265625E-06,当最有意义的位具有32值时,float
中最不重要位的值是最小显着的值,因为它对于47左右的数字而言。)这是float
的最小可能数量。可以在这个规模上改变。
通常,数学库例程难以实现,没有人以正确的舍入(舍入到最接近确切的数学值的代表数字)和已知有限的运行时间。在三角例程中,一些错误的ULP并不罕见。
即使您使用的LIBC代码提供了正确的圆形结果,将其从弧度转换为学位,也会引入两个圆形误差(将180/π转换为代表值并通过它乘以)。期望最终结果是最接近理想数学结果的float
是不合理的。您应该期望几个ULP错误。