先决条件
C++ 不强制要求 IEEE-754 或特定的舍入方法。对于这个答案,我假设 IEEE-754 与二进制格式和四舍五入到最近的平局一起使用。
结论
1/x 如果fabs(x) <= std::ldexp(1, -std::numeric_limits<float>::max_exponent) 溢出。对于常量表达式,您可以使用std::numeric_limits<float>::min()/4。
讨论
在有限范围的末尾进行舍入,就好像指数继续前进一样。例如,使用十进制来说明,如果最高可表示的有限数是 9.99•1017,那么如果指数不受限制,则下一个可表示的数是 1.00•1018。这两者之间的中点是 9.995•1017,因此低于该数字的数字向下舍入,高于该数字的数字向上舍入。平局时,9.995•1017 向上取整。
对于二进制格式,最大可表示值为 (2−ε)•2q,其中 ε 是“机器 epsilon”(1 的 ULP,所以 2-ε 是最大的可表示有效数)并且 q 是最大指数。那么发生舍入的点是 (2−½ε)•2q。
如果 1/x q,则结果向下舍入。否则,向上舍入到∞。因此,结果小于 ∞ 当且仅当 x > 1/((2−½ε)•2q) = 2− q/(2-½ε).
1/(2-½ε) 略大于½,小于½ε,因此小于或等于它的最大可表示值为½。因此,1/x 的结果小于 ∞ 当且仅当 x > 2-q/2 = 2- q-1.
C++ 用std::numeric_limits<double>::max_exponent 告诉我们最大指数(在标题<limits> 中定义)。然而,C++ 将这个指数校准为 [½, 1) 的有效数字范围,而不是 IEEE-754 的 [1, 2),因此它比 q 大一。因此我们想要的-q-1 就是-std::numeric_limits<double>::max_exponent。
我们可以使用ldexp 函数(在<cmath> 中声明)计算2-q-1:std::ldexp(1, -std::numeric_limits<float>::max_exponent)。
使用 Apple Clang 11,此程序:
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
int main(void)
{
float x = std::ldexp(1, -std::numeric_limits<float>::max_exponent);
std::cout << std::setprecision(20) << x << " is too small, result will overflow:\n";
std::cout << "\t" << 1/x << ".\n";
x = std::nexttoward(x, INFINITY);
std::cout << std::setprecision(20) << x << " is just big enough, result will not overflow:\n";
std::cout << "\t" << 1/x << ".\n";
}
产生:
2.9387358770557187699e-39 太小,结果会溢出:
信息。
2.9387372783541830947e-39 刚好够大,结果不会溢出:
3.4028220466166163425e+38。
同样考虑负数,1/x 溢出 iff fabs(x) <= std::ldexp(1, -std::numeric_limits<float>::max_exponent)。
由于 IEEE-754 指定指数范围的方式,std::ldexp(1, -std::numeric_limits<float>::max_exponent) 等于 std::numeric_limits<float>::min()/4。 (IEEE-754 规定最小正态指数为 1-q,所以我们想要的 -q-1 是 (1-q) -2.)