问题 从无符号long long转换为double,反之亦然,可以更改值


在编写C ++代码时,我突然意识到我的数字是错误的 double 至 unsigned long long

具体来说,我使用以下代码:

#define _CRT_SECURE_NO_WARNINGS

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

int main()
{
  unsigned long long ull = numeric_limits<unsigned long long>::max();
  double d = static_cast<double>(ull);
  unsigned long long ull2 = static_cast<unsigned long long>(d);
  cout << ull << endl << d << endl << ull2 << endl;
  return 0;
}

Ideone现场例子

在我的计算机上执行此代码时,我有以下输出:

18446744073709551615
1.84467e+019
9223372036854775808
Press any key to continue . . .

我期望第一和第三个数字完全相同(就像在Ideone上一样),因为我确信这一点 long double 占用了10个字节,并将尾数存储在其中的8个中。我会理解,如果第三个数字与第一个数字相比被截断 - 只是因为我错误的浮点数字格式。但这里的价值是两倍不同!

所以,主要的问题是:为什么?我怎样才能预测出这种情况呢?

一些细节:我在Windows 7上使用Visual Studio 2013,为x86编译,以及 sizeof(long double) == 8 对于我的系统。


11108
2017-11-20 10:57


起源

您的ideone实例中不会出现此问题,因此这可能是MSVC中的错误 - M.M
所以你说你在家用电脑上得到的结果不同于IDE的一个版本? - Tim B
在MSVC中,你仍然遇到问题 numeric_limits<unsigned long long>::max() - 100 ? - M.M
它可能与符号位有关。将您的上一个结果乘以2。 - PaulMcKenzie
这可能是符合规范的行为,因为int在double中并不完全具有代表性 - M.M


答案:


18446744073709551615 并不完全具有代表性 double (在IEEE754中)。这并不是意料之外的,因为64位浮点显然不能代表64位中可表示的所有整数。

根据C ++标准,它是 实现定义 是下一个最高还是下一个最低 double 值被使用。显然在您的系统上,它选择下一个最高值,这似乎是 1.8446744073709552e19。您可以通过输出具有更多精度数字的double来确认这一点。

请注意,这大于原始数字。

将此double转换为整数时,行为将由[conv.fpint] / 1覆盖:

可以将浮点类型的prvalue转换为整数类型的prvalue。转换截断;也就是说,分数部分被丢弃。该 行为未定义 如果截断的值无法在目标类型中表示。

所以这个代码可能会导致 未定义的行为。当发生未定义的行为时,可能发生任何事情,包括(但不限于)虚假输出。


这个问题最初发布于 long double, 而不是 double。在我的gcc上, long double case行为正确,但在OP的MSVC上它给出了相同的错误。这可以通过使用80位的gcc来解释 long double,但MSVC使用64位 long double


12
2017-11-20 11:15





这是由于 double 逼近 long long。它的精度意味着10 ^ 19时约100个单位的误差;当您尝试将值转换为长长范围的上限时,它会溢出。尝试转换10000较低的值而不是:)

顺便说一句,在Cygwin,第三个印刷值为零


1
2017-11-20 11:13



是的,这里应该是溢出,但我不认为它是UB - 我确信在所有合理的平台上,高位都被截断了。我使用的时候你是对的 numeric_limits<unsigned long long>::max() - 10000, 有用。 - alexeykuzmin0
一个人永远不应该依赖它!例如,请注意,它可能是硬件内置操作。 - AndreyS Scherbakov


问题非常简单。这就是发生的事情 你的 案件:

18446744073709551615 当转换为 double 是圆的 向上 到浮点数可以表示的最接近的数字。 (最接近的可表示数字更大)。

当它被转换回一个 unsigned long long,它比 max()。形式上,将此转换回的行为 unsigned long long未定义 但在你的情况下似乎正在发生的事情是环绕。

观察到的显着较小的数字是这样的结果。


1
2017-11-20 11:09



这并不能解释为什么舍入在IDEOne示例上有所不同?或者为什么它发生在max() - 100上 - Tim B
如果它缠绕在mod 2 ^ 64附近,你会得到一个接近0的数字。 - interjay
@TimB,据我所知,在IdeOne中 sizeof(long double)是 12 在我的电脑上,它是 8。 - alexeykuzmin0
@TimB,(i)IDEOne示例最初使用的是long double,(ii)浮点详细信息可能有两种可能性。一个12字节的双精度,具有适当的尾数/指数分割 可以 代表 max() 究竟。 - Bathsheba
1ULL << 64 =因UB而吃掉一只猫。从显而易见的开始。编译器是 不 破碎。 - Bathsheba