[英]`strtof()` conversion error by more than 0.5 ULP
为什么strtof()
"3.40282356779733 650000 e38" 意外地转换为无穷大,即使它在FLT_MAX
的 0.5 ULP 以内?
FLT_MAX
( float32 ) 是 0x1.fffffep+127 或大约 3.4028234663852885981170e+38。
FLT_MAX
上方的 1/2 ULP是 0x1.ffffffp+127 或大约 3.40282356779733 66163754 e+38,因此我希望在“四舍五入到最近”模式下此下方的任何十进制文本和较低的FLT_MAX
转换为FLT_MAX
。
这在十进制文本从FLT_MAX
增加到大约 3.4028235677973388 642700 e38时起作用,但是对于大约高于“3.40282356779733 650000 e38”的十进制文本值,转换结果是infinity 。
以下是揭示问题的代码。 它轻轻地爬上一个十进制文本字符串,寻找 conversion 变为infinity的值。 您的结果可能会有所不同。
#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);
}
Output
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)
注:GNU C11 (GCC) version 11.3.0 (x86_64-pc-cygwin) GNU C version 11.3.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.25编译-GMP
[编辑] FLT_MAX + 1/2 ULP
的精确值:0x1.ffffffp+127 340282356779733 661637539395458142568448 .0
我今天在尝试确定传递给返回有限float
的strtof()
的最大十进制文本时偶然发现了这个问题。
这是一个我可以回答我自己的问题吗? 回答。 欢迎其他答案。
为什么
strtof()
"3.40282356779733 650000 e38" 意外地转换为无穷大,即使它在FLT_MAX
的 0.5 ULP 以内?
当然是双舍入。
这里的“double”是指做某事两次,而不是double
类型。
设高于FLT_MAX
的float
ULP的 1/2 是 0x1.ffffffp+127 或大约 3.40282356779733 66163754 e+38 称为阈值。
关于 3.40282356733 64274808 e38 是低于阈值的double
ULP 的一半。 显然像“3.40282356779733 650000 e38”这样的值过早地四舍五入为double
to threshold 。 作为float
的threshold是FLT_MAX
和下一个更大的float
之间的中间位置(如果扩展了编码)。 作为中途平局,它四舍五入为“偶数”值——在本例中为较大值。 由于下一个较大的float
超出了最大可编码有限值,因此结果为infinity 。
结论
更好的strtof()
可以正确处理这种特殊情况。
相反,将strtof()
中超过FLT_DECIMAL_DIG + 3
(见下文)的小数位视为噪声是合理的。
在替代的strtof()
实现中, IEEE_754允许此类十进制文本转换将所有通过特定重要性的十进制数字视为零。 因此,当接近 2 个float
的 1/2 路点时,允许转换为第二个最接近的float
。 对于普通的float
,意义是FLT_DECIMAL_DIG + 3
或 12 位小数。 这里没有使用,因为第 19 位的小数会影响结果。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.