为什么strtof()
“3.40282356779733 650000 e38”意外地转换为无穷大,即使它在 0.5 ULP 范围内FLT_MAX
?
FLT_MAX
( float32 ) 是 0x1.fffffep+127 或大约 3.4028234663852885981170e+38。
上面的1/2 ULPFLT_MAX
是 0x1.ffffffp+127 或大约 3.40282356779733 66163754 e+38,所以我期望低于此的任何十进制文本以及较低的文本在处于“舍入到最近”模式时FLT_MAX
转换为。FLT_MAX
这是因为十进制文本从3.4028235677973388 642700FLT_MAX
e38 增加到大约 3.4028235677973388 642700 e38 ,但对于高于该值的十进制文本值(如“3.40282356779733 650000 e38”),转换结果为infinity。
下面是揭示问题的代码。它轻轻地爬升十进制文本字符串,寻找转换变为无穷大的值。
您的结果可能会有所不同,因为并非所有 C 实现都使用相同的浮点。
#include <assert.h>
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
void bar(unsigned n) {
char buf[100];
assert (n < 90);
int len = sprintf(buf, "%.*fe%d", n+1, 0.0, FLT_MAX_10_EXP);
puts(buf);
printf("%-*s %-*s %s\n", len, "string", n+3, "float", "double");
float g = 0;
for (unsigned i = 0; i < n; i++) {
for (int digit = '1'; digit <= '9'; digit++) {
unsigned offset = i ? 1+i : i;
buf[offset]++;
errno = 0;
float f = strtof(buf, 0);
if (errno) {
buf[offset]--;
break;
}
g = f;
}
printf("\"%s\" %.*e %a\n", buf, n + 3, g, atof(buf));
}
double delta = FLT_MAX - nextafterf(FLT_MAX, 0);
double flt_max_ulp_d2 = FLT_MAX + delta/2.0;
printf(" %.*e %a FLT_MAX + 1/2 ULP - 1 dULP\n", n + 3, nextafter(flt_max_ulp_d2,0),nextafter(flt_max_ulp_d2,0));
printf(" %.*e %a FLT_MAX + 1/2 ULP\n", n + 3, flt_max_ulp_d2,flt_max_ulp_d2);
printf(" %.*e %a FLT_MAX\n", n + 3, FLT_MAX, FLT_MAX);
printf(" 1 23456789 123456789 123456789\n");
printf("FLT_ROUNDS %d (0: toward zero, 1: to nearest)\n", FLT_ROUNDS);
}
int main() {
printf("%a %.20e\n", FLT_MAX, FLT_MAX);
printf("%a\n", strtof("3.40282356779733650000e38", 0));
printf("%a\n", strtod("3.40282356779733650000e38", 0));
printf("%a\n", strtod("3.4028235677973366163754e+3", 0));
bar(19);
}
Run Code Online (Sandbox Code Playgroud)
输出
0x1.fffffep+127 3.40282346638528859812e+38
inf
0x1.ffffffp+127
0x1.a95a5aaada733p+11
0.00000000000000000000e38
string float double
"3.00000000000000000000e38" 3.0000000054977557577780e+38 0x1.c363cbf21f28ap+127
"3.40000000000000000000e38" 3.3999999521443642490773e+38 0x1.ff933c78cdfadp+127
"3.40000000000000000000e38" 3.3999999521443642490773e+38 0x1.ff933c78cdfadp+127
"3.40200000000000000000e38" 3.4020000005553803402978e+38 0x1.ffe045fe9918p+127
"3.40280000000000000000e38" 3.4027999387901483621794e+38 0x1.ffff169a83f08p+127
"3.40282000000000000000e38" 3.4028200183756559773331e+38 0x1.ffffdbd19d02cp+127
"3.40282300000000000000e38" 3.4028230607370965250836e+38 0x1.fffff966ad924p+127
"3.40282350000000000000e38" 3.4028234663852885981170e+38 0x1.fffffe54daff8p+127
"3.40282356000000000000e38" 3.4028234663852885981170e+38 0x1.fffffeec5116ep+127
"3.40282356700000000000e38" 3.4028234663852885981170e+38 0x1.fffffefdfcbbcp+127
"3.40282356770000000000e38" 3.4028234663852885981170e+38 0x1.fffffeffc119p+127
"3.40282356779000000000e38" 3.4028234663852885981170e+38 0x1.fffffefffb424p+127
"3.40282356779700000000e38" 3.4028234663852885981170e+38 0x1.fffffeffffc85p+127
"3.40282356779730000000e38" 3.4028234663852885981170e+38 0x1.fffffefffff9fp+127
"3.40282356779733000000e38" 3.4028234663852885981170e+38 0x1.fffffefffffeep+127
"3.40282356779733600000e38" 3.4028234663852885981170e+38 0x1.fffffeffffffep+127
"3.40282356779733640000e38" 3.4028234663852885981170e+38 0x1.fffffefffffffp+127 <-- Actual
"3.40282356779733660000e38" 3.4028234663852885981170e+38 ... <-- Expected
"3.40282356779733642000e38" 3.4028234663852885981170e+38 0x1.fffffefffffffp+127
"3.40282356779733642700e38" 3.4028234663852885981170e+38 0x1.fffffefffffffp+127
3.4028235677973362385861e+38 0x1.fffffefffffffp+127 FLT_MAX + 1/2 ULP - 1 dULP
3.4028235677973366163754e+38 0x1.ffffffp+127 FLT_MAX + 1/2 ULP
3.4028234663852885981170e+38 0x1.fffffep+127 FLT_MAX
1 23456789 123456789 123456789
FLT_ROUNDS 1 (0: toward zero, 1: to nearest)
Run Code Online (Sandbox Code Playgroud)
注:GNU C11 (GCC) 版本 11.3.0 (x86_64-pc-cygwin) 由 GNU C 版本 11.3.0、GMP 版本 6.2.1、MPFR 版本 4.1.0、MPC 版本 1.2.1、isl 版本 isl-0.25 编译-GMP
[编辑] 的确切值FLT_MAX + 1/2 ULP
: 0x1.ffffffp+127 340282356779733 661637539395458142568448 .0
今天,当我尝试确定传递给返回有限值的最大十进制文本 strtof()
float
时,我偶然发现了这个问题。
这是一个我可以回答我自己的问题吗?回答。欢迎其他答案。
为什么
strtof()
“3.40282356779733 650000 e38”意外地转换为无穷大,即使它在 0.5 ULP 范围内FLT_MAX
?
当然是双舍入。
这里的“双”是指做某件事两次,而不是类型double
。
令上面ULP的 1/2为 0x1.ffffffp+127 或大约 3.40282356779733 66163754 e+38 称为阈值。float
FLT_MAX
大约 3.40282356733 64274808double
e38 是低于阈值的 ULP的一半。显然,像“3.40282356779733 650000 e38”这样的值会过早舍入为阈值。 Threshold,作为 a ,是和下一个较大值之间的一半(如果编码被扩展)。作为半平局,它四舍五入到“偶数”值 - 在本例中是较大的值。由于下一个较大值超出了最大可编码有限值,因此结果为无穷大。double
float
FLT_MAX
float
float
结论
更好的方法strtof()
可以正确处理这个极端情况。
FLT_DECIMAL_DIG + 3
相反,将过去的小数位(见下文)视为噪音是合理的strtof()
。
在替代strtof()
实现中,IEEE_754允许这种十进制文本转换,以将传递一定意义的所有十进制数字视为零。这样,float
当接近 2 秒的 1/2 路点时,允许转换到第二个最接近的值float
。对于 common float
,其重要性为FLT_DECIMAL_DIG + 3
或 12 位小数。这里没有使用它,因为第 19 位的小数会影响结果。