所有整数值都可以完美地表示为双精度浮点数吗?

70

我的问题是:是否保证所有整数值都有完美的双精度表示。

考虑以下输出“Same”的代码示例:

// Example program
#include <iostream>
#include <string>

int main()
{
  int a = 3;
  int b = 4;
  double d_a(a);
  double d_b(b);

  double int_sum = a + b;
  double d_sum = d_a + d_b;

  if (double(int_sum) == d_sum)
  {
      std::cout << "Same" << std::endl;
  }
}

对于任何架构、编译器,以及任何 ab 的值,这是否保证为真?将任何整数 i 转换为 double,它总是以 i.0000000000000 而不是例如 i.000000000001 的方式表示?

我尝试了一些其他数字,它总是成立的,但找不到关于这是否是巧合或设计的任何信息。

注意:这与 这个问题 不同(除了语言),因为我正在将两个整数相加。


4
您可以使用循环来测试每个可能的数字。 - mch
16
答案简短明了:"不行" - intdouble 可以表示的值范围由实现定义,但是 double 绝不能支持它所能表示的所有整数值。实际答案是 "这取决于情况"。 - Peter
5
@mch: 这并不能证明什么,这可能对我的架构是正确的,但对其他架构可能是错误的... - Thomas
4
对于任何架构、编译器和a&b的任何值,这是否保证为真?不是的。据我所知,C++没有为浮点数值指定任何特定的二进制表示。当然,在实践中,您可能可以依赖大多数平台上的大多数编译器使用IEEE 754浮点数。 - sigbjornlo
18
64位IEEE双精度浮点数无法表示的最小正整数为9007199254740993。该整数有54个有效位,而双精度浮点数只能表示53个有效位。请注意,9007199254740992在二进制补码中需要54位才能表示。但是双精度浮点格式只需1个比特位(实际上是零,因为前导位是隐含的)即可表示它的尾数。不需要循环查找此信息。使用numeric_limits<double>::digitsnextafter即可。 - Howard Hinnant
显示剩余5条评论
5个回答

85

免责声明(由Toby Speight建议):虽然IEEE 754表示法非常常见,但实现可以使用任何其他符合语言要求的表示法。


双精度浮点数的表示形式为mantissa * 2^exponent,即一些位用于浮点数的小数部分。

             bits        range                       precision
  float        32        1.5E-45   .. 3.4E38          7- 8 digits
  double       64        5.0E-324  .. 1.7E308        15-16 digits
  long double  80        1.9E-4951 .. 1.1E4932       19-20 digits

IEEE 754双精度类型示意图

小数部分也可以通过使用指数来表示整数,指数将小数点后的所有数字都移除。

例如:2,9979 · 10^4 = 29979。

由于常规的int通常为32位,因此您可以将所有的int表示为double类型,但对于64位整数来说显然不再适用。更准确地说(正如LThode在评论中指出的):IEEE 754双精度类型可保证最多53个二进制位(52个有效数字位+1个隐式前导位)。

答案:32位int合适,64位int不合适。

(这适用于服务器/台式机通用CPU环境,但其他架构可能会有所不同。)

实际答案:如Malcom McLean所说,64位double类型对于几乎所有可能计数实际事物的整数都是足够的。


对于实证倾向的人,请尝试这个

#include <iostream>
#include <limits>
using namespace std;

int main() {
    double test;
    volatile int test_int;
    for(int i=0; i< std::numeric_limits<int>::max(); i++) {
        test = i;
        test_int = test;

        // compare int with int:
        if (test_int != i)
            std::cout<<"found integer i="<<i<<", test="<<test<<std::endl;
    }
    return 0;
}

成功时间:0.85 内存:15240 信号:0


子问题:关于分数差异的问题。是否可能存在一个整数,它转换为一个浮点数时,其值偏离正确值,但是由于四舍五入而再次转换为相同的整数?

答案是否定的,因为任何转换回和转换前值相同的整数,在双精度浮点数中实际上表示的是相同的整数值。对我来说,最简单的解释(由 ilkkachu 提出)是使用指数 2 ^ exponent,步长必须始终是二的幂次方。因此,在超过最大的52(+1 sign)位整数后,从来没有两个距离小于2的双精度浮点数,这解决了舍入问题。


3
这个比较操作将始终返回“false”,因为在这个比较中,整数test_int会被隐式转换为一个“双精度浮点数”。因此,即使“双精度浮点数”无法表示该“整数”,该“整数”也会被转换为相同的不精确表示形式,然后进行相等比较。 - Corristo
1
@Corristo,比较中的两个数字都是int类型,为什么要将任何东西转换为double?我不明白,你能详细解释一下吗? - Beginner
1
@初学者基本上是这样,但为了完整起见,可能会有一些边缘情况:比如说1(int)转换为1.0000001。如果您将其作为整数添加一百万次,您将(正确地)得到一百万。如果在此之前将其转换为double,则会得到1000001。 - Thomas
IEEE 754双精度可以保证高达53位(52位有效数字+隐式前导1位)的精度。 - LThode
你有53位二进制数。如果你需要一个大于2^53的整数,就必须问它代表什么意义。 - Malcolm McLean
显示剩余16条评论

19

假设您有一个64位整数类型和一个64位浮点类型(这对于double来说是典型的)。该整数类型有2^64个可能的值,该浮点类型也有2^64个可能的值。但是其中一些浮点值(实际上,大多数)不表示整数值,因此浮点类型能够表示的整数值比整数类型能够表示的要少。


4
32位整数怎么处理?基数参数在这种情况下无法得出确切结论。 - Beginner
2
@初学者 - 更极端的是,在没有奇怪的浮点类型的情况下,可以将每个可以表示为int8_t值的整数值(假设该类型实际存在)完全表示为double。但问题比这更广泛;它询问是否所有整数值都可以精确地表示为double,答案是“不行”。而对隐含问题的答案是,如果您需要知道哪些整数可以在double中精确表示,则必须了解有关系统上如何表示double的相当详细的细节。这不适合初学者。 - Pete Becker
1
无论是不是反常的,任何小于10^6-1的整数值都需要被精确地表示为浮点数,而任何小于10^10-1的整数值都需要被表示为双精度浮点数。至少在C语言中是这样的。不仅int8_t,而且int16_t和对于double来说,int32_t(因此int和long所需的最小范围)也都在这个范围内。 - Random832

12
答案是否定的。这仅在int为32位时才有效,而这只是大多数平台上都成立的,但并不被标准保证。
两个整数可以共享相同的双精度表示。
例如,this
#include <iostream>
int main() {
    int64_t n = 2397083434877565865;
    if (static_cast<double>(n) == static_cast<double>(n - 1)) {
        std::cout << "n and (n-1) share the same double representation\n";
    }
}    

将打印

n和(n-1)共享相同的双精度表示

即,2397083434877565865和2397083434877565864都将转换为相同的double

请注意,我在此处使用int64_t来保证64位整数,这可能也是您的平台上int的情况。


@Corristo,OP的问题是关于整数的。当您使用64位整数时,应该使用long double,这样它就可以再次适用于整个范围。 - Rene
@Corristo 是的,但根据这个问题,https://dev59.com/qGHVa4cB1Zd3GeqPnX07,有两个这样的平台。因此,考虑到这些,答案实际上是“不”。 (除非它们也有一个128位的double,我找不到这个信息) - Rene
2
@Thomas -- 浮点精度并不会随着整数的增大而变差。它保持完全相同。你的整数值似乎具有更高的精度,因为你写了更多的非零数字,但是它们的精度也不会改变。 - Pete Becker
1
@Kundor -- 是的,这很清楚,但是它是完全错误的。想要有效地使用浮点数学的程序员需要了解其工作原理,并且不应该基于误导性的陈词滥调做出决策。 - Pete Becker
1
@PeteBecker:这是意义,而不是精度。“精确到0.1”并不意味着一个值n在除以n后在0.1范围内,它意味着它在正确值的0.1范围内。说一个城镇有12,000人比说一个房间里有12个人“不够精确”,当两个数字都被理解为有2个有效数字时。要用与房间相同的“精度”给出城镇中的人数,您需要使用更多有效数字,并说有12,132人。 - Nick Matteo
显示剩余4条评论

3
您有两个不同的问题:
别人已经回答了一个问题(简而言之:这取决于int和double的精度)。
您的代码将两个int相加,然后将结果转换为double。对于某些值,int的总和将溢出,但两个单独转换的double的总和通常不会溢出。对于这些值,结果会有所不同。

这是我提问时没有考虑到的事情,但仍然是一个很好的观点 :) - Thomas

2
简短的回答是“可能”。通用回答是“不是所有的平台都可以”。
这主要取决于您的平台,特别是以下因素:
1. `double` 的大小和表现形式 2. `int` 的范围
对于使用 IEEE-754 双精度浮点数的平台,如果 `int` 是 53 位或更小,则可能成立。对于 `int` 大于 `double` 的平台,显然不成立。
您可以通过使用 `std::numeric_limits` 和 `std::nextafter` 来调查运行时主机的属性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接