浮点数不确定度分析疑问:FLT_EPSILON测试结果与预期不符
先澄清最关键的错误:FLT_EPSILON的定义是1.0到下一个可表示的float类型值的差值,它并不是所有浮点数的相邻可表示值之间的间隔(也就是ULP,Unit in the Last Place)。浮点数的间隔是随数值大小变化的——数值越大,相邻两个可表示浮点数的差距越大,因为浮点数是采用对数刻度存储的。
为什么你的测试代码没看到预期变化?
你的测试有两个容易忽略的问题:
1. printf的输出精度掩盖了浮点数的实际变化
你用printf("%f")输出时,默认只保留6位十进制小数。当你给1.234567f加上1.192092896e-7f后,浮点数确实发生了变化,但这个变化太小,在6位小数的精度下被四舍五入掩盖了。试试改成更高精度的输出:
printf("Inicial:\t%.12f\n", f1); printf("Inicial:\t%.12f\n", f2); printf("Inicial:\t%.12f\n\n", f3);
你会看到f1、f2、f3的实际值差异:f2会比f1大一个ULP,而f3和f1相等。
2. 你添加的数值刚好没触发浮点数的进位
FLT_EPSILON的数值是1.1920928955078125e-7,刚好是1.0的ULP。1.234567f在1.0到2.0之间,它的ULP和FLT_EPSILON是同一个数量级,但你加的1.192092895e-7f略小于这个ULP,所以运算后会被舍回原来的1.234567f;只有当你添加的数值大于等于该数的ULP时,才会得到下一个可表示的浮点数。
解答你的核心问题:求最大的y使得x+y和x-y都等于R
设R是x对应的浮点数,你要找的y是R的ULP的一半,也就是ULP(R)/2。
原因是:浮点数的默认舍入模式是“向最近的可表示值舍入,若刚好在中间则向偶数舍入”。当你给R加减一个小于ULP(R)/2的数时,结果会被舍回R;如果加减的数等于ULP(R)/2,则取决于R的最后一位二进制位是否为偶数(偶数则保留R,奇数则跳到相邻值);如果大于ULP(R)/2,则会变成相邻的可表示浮点数。
你可以用C标准库的nextafterf函数计算任意float的ULP:
#include <math.h> #include <float.h> float R = 1.234567f; float next_R = nextafterf(R, INFINITY); // 获取R的下一个可表示浮点数 float ulp_R = next_R - R; float y = ulp_R / 2.0f; // 这就是你要找的最大y值
内容的提问来源于stack exchange,提问作者Pedro Pereira

