关于机器epsilon运算中加法结合性及结果异常的技术问询
浮点数运算中的结合律问题与机器epsilon困惑
我知道接近零的算术运算有时不满足结合律,这根源在于机器epsilon的定义。但我用机器epsilon测试一组等式时,得到了意外的结果:
//just calculate float epsilon_float = 1.0f; auto counter_float = 0; while ((epsilon_float / 2 + 1.0f) != 1.0f) { epsilon_float /= 2; counter_float += 1; } std::cout << (1 < 1 + epsilon_float) << std::endl; std::cout << (1 == 1 + epsilon_float / 2) << std::endl; std::cout << (1 + epsilon_float > 1 + epsilon_float / 2) << std::endl; // By definition std::cout << (1 + epsilon_float + epsilon_float / 2 > 1 + epsilon_float / 2 + epsilon_float) << std::endl; // Non-associativity std::cout << (1 + epsilon_float < 1 + epsilon_float + epsilon_float / 2) // its true << std::endl;
我无法理解为何最后那个不等式1 + epsilon_float < 1 + epsilon_float + epsilon_float / 2成立——按我的理解,机器epsilon是和1相加后结果不等于1的最小数值,但这里epsilon/2参与运算后,居然让结果比1 + epsilon_float更大了。
问题解析
首先明确几个关键事实:
- 对于单精度
float,机器epsilon的值是2^-23,它是满足1.0f + eps != 1.0f的最小正浮点数。 epsilon_float / 2 = 2^-24,这个值和1相加时,会因为浮点数的尾数精度限制被舍入,所以1 + epsilon_float/2 == 1,这和测试结果一致。
但当计算1 + epsilon_float + epsilon_float/2时,运算顺序是先得到x = 1 + epsilon_float = 1 + 2^-23,再计算x + 2^-24,此时情况完全不同:
x的二进制表示为1.000...0001(23个0后接一个1),属于指数为0的浮点数区间(数值在[1,2)之间)。- 浮点数在这个区间的相邻可表示值间隔是
2^-23(也就是epsilon_float),但x + 2^-24的值是1 + 3*2^-24,刚好处于x和下一个可表示值1 + 2^-22的中间位置。 - 按照浮点数默认的向最近偶数舍入规则,这个中间值会被舍入到更大的那个可表示值
1 + 2^-22,而1 + 2^-22显然大于x = 1 + 2^-23,因此最后那个不等式成立。
简单来说:epsilon_float/2和1相加会被“吞掉”,但和1 + epsilon_float相加时,因为当前的基数已经比1大了一个epsilon,这个小值足以触发舍入,让结果跳到下一个更大的可表示浮点数。
内容的提问来源于stack exchange,提问作者user24722497
相关产品推荐
相关产品推荐

