你能给我一个16位数(或更多)十进制数,只能在15日正确地进行双精度浮点数转换吗?

时间:2017-11-07 10:51:53

标签: c++ floating-point

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

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

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

你能给我一个吗?或者你会如何找回它?

4 个答案:

答案 0 :(得分:4)

90071992547409929007199254740993都是16位数字,当存储为IEEE754双倍时,它们都具有值9007199254740992

即。 9007199254740993的第16位是一个笑话。

我选择这个例子的灵感是9007199254740992是2的第54个幂,就在IEEE754 double类型的有效位数中的位数之后,第一个十进制数字恰好是{{1 }}。因此,尽管只有16位数字,但这个上面的奇数都没有代表!

坚持IEEE754双精度,如果你想要一个0到1范围内的例子,那么从二元有理9开始,并添加一个0.75阶的值。很快,您会偶然发现1e-160.7500000000000005 0.7500000000000006

答案 1 :(得分:1)

我已经详细说明了(感谢@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;        
    }
}

答案 2 :(得分:1)

  

你能给我一个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 Failed\n");
}

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.

答案 3 :(得分:0)

根据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;
}