错误的双精度转长整型操作

11
这主要是对 另一个问题 的跟进,那个问题涉及到对于大值从 long 转换为 double 再转回 long 时出现的奇怪情况。
我已经知道将浮点数转换为整数类型会进行截断。如果被截断的值不能在目标类型中表示,则行为未定义:

4.9 浮点-整数转换 [conv.fpint]

浮点类型的 prvalue 可以转换为整数类型的 prvalue。转换会截断;也就是说,小数部分会被舍去。如果被截断的值不能在目标类型中表示,则行为未定义。

以下是我的代码来演示这个问题,假设使用 little endian 架构,其中 long long 和 long double 都使用 64 位:
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
  unsigned long long ull = 0xf000000000000000;
  long double d = static_cast<long double>(ull);
  // dump the IEE-754 number for a little endian system
  unsigned char * pt = reinterpret_cast<unsigned char *>(&d);
  for (int i = sizeof(d) -1; i>= 0; i--) {
      cout << hex << setw(2) << setfill('0') << static_cast<unsigned int>(pt[i]); 
  }
  cout << endl;
  unsigned long long ull2 = static_cast<unsigned long long>(d);
  cout << ull << endl << d << endl << ull2 << endl;
  return 0;
}

输出结果为(在旧的XP 32位机器上使用MSVC 2008 32位版):

43ee000000000000
f000000000000000
1.72938e+019
8000000000000000

值的说明:

  • 0xf000000000000000 在十进制中为17293822569102704640,因此转换成 double 是正确的。
  • 43ee000000000000:尾数部分是e000000000000,加上隐含的1,它正确表示了带有 10 的 4 位 - 指数为43e,去除3ff偏差后,它给出了一个二进制表示式1.111 263,所以精确表示了0xf000000000000000或17293822569102704640(参考)。

由于该值可以表示为 unsigned long long,我期望将其转换为 unsigned long long 会得到原始值,而 MSVC 给出的结果是0x8000000000000000或9223372036854775808。

问题是:这个转换是否导致未定义行为,正如另一个问题的被接受答案所建议的那样,还是这真的是 MSVC 的一个 bug?

(注意:在 FreeBSD 10.1 上使用 CLang 编译器的相同代码会给出正确的结果)

参考资料,我找到了生成的代码:

  unsigned long long ull2 = static_cast<unsigned long long>(d);
0041159E  fld         qword ptr [d] 
004115A1  call        @ILT+490(__ftol2) (4111EFh) 
004115A6  mov         dword ptr [ull2],eax 
004115A9  mov         dword ptr [ebp-40h],edx 

而 _ftol2 的代码似乎是这样的(从执行时调试器获取):

00411C66  push        ebp  
00411C67  mov         ebp,esp 
00411C69  sub         esp,20h 
00411C6C  and         esp,0FFFFFFF0h 
00411C6F  fld         st(0) 
00411C71  fst         dword ptr [esp+18h] 
00411C75  fistp       qword ptr [esp+10h] 
00411C79  fild        qword ptr [esp+10h] 
00411C7D  mov         edx,dword ptr [esp+18h] 
00411C81  mov         eax,dword ptr [esp+10h] 
00411C85  test        eax,eax 
00411C87  je          integer_QnaN_or_zero (411CC5h) 
00411C89  fsubp       st(1),st 
00411C8B  test        edx,edx 
00411C8D  jns         positive (411CADh) 
00411C8F  fstp        dword ptr [esp] 
00411C92  mov         ecx,dword ptr [esp] 
00411C95  xor         ecx,80000000h 
00411C9B  add         ecx,7FFFFFFFh 
00411CA1  adc         eax,0 
00411CA4  mov         edx,dword ptr [esp+14h] 
00411CA8  adc         edx,0 
00411CAB  jmp         localexit (411CD9h) 
00411CAD  fstp        dword ptr [esp] 
00411CB0  mov         ecx,dword ptr [esp] 
00411CB3  add         ecx,7FFFFFFFh 
00411CB9  sbb         eax,0 
00411CBC  mov         edx,dword ptr [esp+14h] 
00411CC0  sbb         edx,0 
00411CC3  jmp         localexit (411CD9h) 
00411CC5  mov         edx,dword ptr [esp+14h] 
00411CC9  test        edx,7FFFFFFFh 
00411CCF  jne         arg_is_not_integer_QnaN (411C89h) 
00411CD1  fstp        dword ptr [esp+18h] 
00411CD5  fstp        dword ptr [esp+18h] 
00411CD9  leave            
00411CDA  ret 

1
看看编译器生成的代码会很有趣,以了解它到底在做什么。 - Mark Ransom
1
MSVC 14 (VS2015):43ee000000000000f0000000000000001.72938e+019f000000000000000 - Simon Kraemer
1
在VS 2013 64位中,f00... - Petr
1
使用VS2010(64位)来编写f00... - Simon Kraemer
1
所以,当比较32位和64位可执行文件时,VS2010会得出不同的结果... - Simon Kraemer
显示剩余12条评论
1个回答

1
这主要是对问题的评论汇编。
旧版MSVC似乎会错误地处理将64位整数转换为64位双精度数字的过程。
该漏洞存在于2008年以下的版本中。
MSCV 2010在32位模式下有误,在64位模式下正确。
从2012年开始的所有版本都是正确的。

非常感谢提供的信息。似乎在一个大项目(pypy)内使用帮助函数而不是直接转换确实可以解决问题。https://bitbucket.org/pypy/pypy/commits/1512e98263d9 - Armin Rigo

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