32-битные числа с плавающей запятой хранятся в 32 битах, что означает, что не может быть намного больше, чем приблизительно 4 миллиарда различных значений. Компьютеры достаточно быстры, чтобы выполнять итерацию по всем числам, поэтому поиск с помощью грубой силы 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 \0.
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 %d\n", 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.111294307f, которое имеет то же десятичное представление при преобразовании в строку только с 8 значащими цифрами.
В целом программа считает, что существует около 2,14 миллиарда положительных чисел с плавающей запятой , но только около 32 миллионов из них нуждаются в 9 значащих десятичных разрядах для однозначного представления. Это соответствует примерно 1,5% всех чисел, для которых нужны 9 цифр, что объясняет, почему при ручном тестировании их вряд ли можно найти:
Ясно, что можно вручную проверять значения с плавающей запятой, десятичные представления которых начинаются с ди git 1, потому что для них вам понадобится еще один значащий десятичный знак di git для ведущего 1 по сравнению с предыдущими значениями очень похожего значения, которые начинаются с di git 9. Однако существуют также степени 10, для которых не существует значения с плавающей запятой, которое преобразуется в десятичное значение 1.xxx * 10 ^ yy, для которого фактически требуется 9 значащих ди git. Эти степени 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, что, вероятно, является наиболее вероятным местом для людей, чтобы начать ручной поиск.