您能否给我一个 16 位(或更多)十进制数,该十进制数仅在第 15 位正确转换为双精度浮点圆



对于单精度,最小位数保证为 6

9999978e39999979e3都将"收敛"到9999978496.因此,无论我将使用什么小数,6 位数字始终由单精度浮点数学保证(至少对于 IEEE 754 )。

我认为同样适用于双精度,但最小值应为 15。我找不到证明这一点的十进制数,就像上面使用单精度一样。

你能给我一个吗?或者你会如何检索它?

90071992547409929007199254740993 都是 16 位数字,当存储为双精度IEEE754时,两者都具有值9007199254740992

9007199254740993的第 16 位数字是一个笑话。

我选择这个例子的灵感是,9007199254740992是 2 的 54 次方,紧随IEEE754双精度类型的有效位数之后,第一个十进制数字恰好是9。因此,尽管只有 16 位数字,但上面的奇数都无法表示!


坚持IEEE754双精度,如果你想要一个 0 到 1 范围内的例子,那么从二元有理0.75开始,添加一个 order 1e-16 的值。很快,你会偶然发现0.75000000000000050.7500000000000006,它们都是0.75000000000000055511151231257827021181583404541015625

我已经详细说明(感谢@Bathsheba提示)一种算法,从小数部分开始,按所需的数字(在我的例子中是第 16 位)递增,将找到(对于以下 10000 位小数)小数,这些小数将与相同的二进制双精度IEEE754表示相冲突。随意调整它:

#include <iostream>
int main() {
    std::cout.precision(100);
    long long int decimalPart = 7500000000000005;
    double value, temp = 0.0;
    // add 1e-16 increment
    for(int i = 0; i < 10000; i++) {
        value = decimalPart / 1e16;
        // found
        if(temp == value) {
            std::cout << "decimal found: 0." << decimalPart << std::endl;
            std::cout << "it collides with: 0." << decimalPart - 1 << std::endl;
            std::cout << "both stored (binary) as " << value << std::endl << std::endl;
        }        
        decimalPart += 1;
        temp = value;        
    }
}

您能否给我一个 16 位(或更多)十进制数,该十进制数仅在第 15 位正确转换为双精度浮点圆?

这样的数字并不罕见,所以很容易尝试限制在感兴趣范围内的各种字符串。

在 16 位十进制文本值的广泛范围内,大约 10% 失败。所有失败都以 '4' 或更多的前导数字开始 - 这并不奇怪。

// Although a C++ post, below is some C code
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void form_text_number(char *buf, int significant_digits, int min_expo, int max_expo) {
  unsigned i = 0;
  buf[i++] = (char) (rand() % 9 + '1');
  buf[i++] = '.';
  for (int sd = 1; sd < significant_digits; sd++) {
    buf[i++] = (char) (rand() % 10 + '0');
  }
  sprintf(buf + i, "e%+03d", rand() % (max_expo - min_expo + 1) + min_expo);
}
bool round_trip_text_double_text(const char *s, int significant_digits) {
  double d = atof(s);
  char buf[significant_digits + 10];
  sprintf(buf, "%.*e", significant_digits - 1, d);
  if (strcmp(s, buf)) {
    printf("Round trip failed "%s" %.*e "%s"n", s, significant_digits - 1 + 3,d, buf);
    return false;
  }
  return true;
}

测试代码

void test_sig(unsigned n, int significant_digits, int min_expo, int max_expo) {
  printf("Sig digits %2d: ", significant_digits);
  while (n-- > 0) {
    char buf[100];
    form_text_number(buf, significant_digits, min_expo, max_expo);
    if (!round_trip_text_double_text(buf, significant_digits)) {
      return;
    }
  }
  printf("None Failedn");
}
int main(void) {
  test_sig(10000, 16, -300, 300);
  test_sig(10000, 16, -1, -1);
  test_sig(1000000, 15, -300, 300);
  test_sig(1000000, 15, -1, -1);
  return 0;
}

输出

Sig digits 16: Round trip failed "8.995597974696435e+110" 8.995597974696434373e+110 "8.995597974696434e+110"
Sig digits 16: Round trip failed "6.654469376627144e-01" 6.654469376627144550e-01 "6.654469376627145e-01"
Sig digits 15: None Failed
Sig digits 15: None Failed

备注:当许多失败字符串的double打印为3个额外的数字时,这3位数字在445到555的范围内。

根据 IEEE 754,有效位数(或尾数)有 52 个显式位和一个隐式额外位。因此,所有 53 位的整数都精确地表示为 double 。54 位或更多位的整数将丢失低位,因此如果该位不为零,它们将无法精确表示。因此,未精确表示为double的最小整数是1ULL << 53 + 1

显示它的程序:

#include <iostream>
#include <cstdint>
int main(int, char**) {
    std::uint64_t i = (1ULL << 53) + 1;
    double x = i;
    std::uint64_t j = x;
    std::cout << x << " " << i << " " << j << std::endl;
    return 0;
}