我在这篇关于单精度浮点数的维基百科文章中发现了以下语句https://en.wikipedia.org/wiki/Single-precision_floating-point_format:
如果IEEE 754单精度数字转换为十进制至少有9个有效数字的字符串,然后转换回单精度表示,最终结果必须与原始编号。
我试图找到单精度浮点数字的例子,这些数字实际上需要9个有效的十进制数字,并且只有8个有效的数字还不明确,而且没有找到任何数字,例如,通过在gdb调试器中打印浮点值,或者通过尝试将不同的值转换为倍频程中的单精度,但是还没有发现需要多于8个十进制数字才能具有与其直接相邻浮点值不同的十进制表示的示例。
问题是,是否真的存在需要9位小数的单精度(32位)浮点值,或者这只是一个永远不需要的安全上限。您能举一个单精度浮点值的例子吗?当转换为只有8位有效十进制数字,然后再转换回二进制浮点表示时,它的值与原始浮点值不同。
32位浮点数字存储在32位中,这意味着不同的值不能超过大约40亿。计算机的速度足够快,可以迭代所有数字,因此,对32位浮点数字的强力搜索可以在可接受的时间内实现自动化,并测试所有可能的数字,如果转换为只有8个有效十进制数字的字符串加上从字符串到单精度浮点表示的反向转换会改变值。
以下简短的C++程序对所有正浮点值执行此操作:
#include <cstdio>
#include <cmath>
#include <limits>
#include <cinttypes>
int main(int argc, char**argv) {
// Test if conversion with /precision/ significant decimal digit is enough
int precision = 8;
// Can override precision = 8 with a command line parameter
if (argc > 1) {
precision = strtol(argv[1], nullptr, 0);
if (precision < 1 || precision > 50) {
printf("Error: precision should be between 1 and 50, got %d.n",
precision);
exit(1);
}
}
// Buffer length of character buffers to store string representations of
// floating point numbers with /precision/ significant digits. /buflen/ is
// larger than /precision/ because it also needs to store leading zeros,
// decimal point, sign, scientific notation exponents, and terminating .
const int buflen = precision + 10;
// storage for current number converted to string with 8 decimal digits
char str[buflen] = "";
// shorthands for maxfloat and infinity
const float maxfloat = std::numeric_limits<float>::max();
const float inf = std::numeric_limits<float>::infinity();
// Count the number of times where /precision/ was not sufficient
uint64_t num_clashes_found = 0;
// Count all tested floats
uint64_t num_floats_tested = 0;
// loop over all positive single precision floating point numbers
for (float f = 0.0f; // start with zero
f <= maxfloat; // iterate up to and including maxfloat
++num_floats_tested, // count the number of all tested floats
f = nextafterf(f, inf)) // increment f to next larger float value
{
// convert number to string with /precision/ significant decimal digits
int numprintedchars = snprintf(str, buflen, "%.*g", precision, f);
// If string buffer is not long enough to store number as string with
// /precision/ significant digits, then print warning and terminate program
if (numprintedchars >= buflen) {
printf("Buffer length %d is not enough to store "%.*g", should"
" be at least %dn", buflen, precision, f, numprintedchars+1);
exit(1);
}
// convert the string back to float
float float_from_string = strtof(str,nullptr);
// Compare the value
if (f != float_from_string) {
printf("%.*g converts to "%s" which reads back as %.*g.n",
precision+1, f, str, precision+1, float_from_string);
++num_clashes_found;
}
}
printf("Found %" PRIu64" clashes when using %d significant decimal digits.n",
num_clashes_found, precision);
printf("Total number of tested floats is %" PRIu64", i.e. with %d significant"
" decimal digits, we get clashes in %g%% of all numbers.n",
num_floats_tested, precision,
100.0 / num_floats_tested * num_clashes_found);
return 0;
}
这个程序需要大约20分钟的时间来迭代所有正的单精度浮点数。
它找到的一个示例数字是0.111294314f。当转换为具有8个有效数字的十进制字符串时,结果是"0.11129431"。下一个较小的单精度浮点数字是0.1112.94307f,当转换为只有8个有效位数的字符串时,它具有相同的十进制表示。
总的来说,该程序统计出大约有21.4亿个正浮点数,但其中只有大约3200万个需要9个有效的十进制数字才能明确表示。这相当于需要9位数字的所有数字的1.5%,这解释了为什么手动测试不太可能找到它们:
很明显,可以手动测试小数表示以数字1开头的浮点值,因为对于这些浮点值,与之前以数字9开头的值非常相似的值相比,前导1需要多一个有效的小数位数。然而,也有10的幂,对于它,不存在转换为十进制1.xxx*10^yy的浮点值,它实际上需要9个有效数字。这些10的幂,其中8个有效数字总是足够的(给出了10的指数,命名为yy):-34,-31,-21,-18,-15,-12,-09,-06,-05,-03,+00,+07,+08,+10,+13,+16,+19,+22,+25,+28。如果碰巧手动测试这些10次方中的任何一次方附近的值,则无法找到阳性结果。这包括10^0,即接近1.0的值,这可能是人类最有可能开始手动搜索的地方。
是否存在需要9位小数的单精度(32位)浮点值:OP
我试图找到单精度浮点数的例子,这些浮点数实际上需要9个有效的十进制数字,并且只有8个有效数字还不明确,而且还没有找到任何:OP
鸽子洞原理
示例:在8和16之间,由于公共float
的二进制编码,存在223不同的float
线性分布。其中1/8在[10和11之间):220或1048576个不同的值。前两个十进制数字是10
。像10.xxx xxx
中那样多使用6个十进制数字只会产生1000000种不同的组合,但我们需要1048576。CCD_ 5值中的48576个与其它值中的485 76个相冲突。需要另一个十进制数字。更多详细信息
C规范使用以下2 FP基数来查找XXX_DECIMAL_DIG
,即9 OP寻道。
小数位数
n
,这样,任何以p
为基数的b
位数的浮点数都可以四舍五入为以n
为小数位数的浮整数,然后在不更改值的情况下再次返回,C17dr§5.2.4.2.2 11
对于float
,使用p == 24
作为其编码的24个有效数字。(23明确)。
FLT_DECIMAL_DIG
=
上限(1+p最大*log102)
天花板(1+24*0.3010…)
天花板(8.224…)
9