不正确的双长转换 [英] Incorrect double to long conversion

查看:32
本文介绍了不正确的双长转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这主要是对另一个问题的跟进,这是关于一个奇怪的转换从 long 到 double 再回到 long 以获得大值.

This is mainly a followup to this other question, that was about a weird conversion from long to double and back again to long for big values.

我已经知道将浮点数转换为整型确实会截断,如果截断的值无法在目标类型中表示,则行为未定义:

I already know that converting a float to an integral type does truncate, if that is the truncated value cannot be represented in target type, the behaviour is undefined:

4.9 浮点积分转换 [conv.fpint]

4.9 Floating-integral conversions [conv.fpint]

浮点类型的纯右值可以转换为整数类型的纯右值.转换截断;也就是说,小数部分被丢弃.如果不能截断的值,则行为未定义以目的地类型表示.

A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.

但这里是我的代码来演示这个问题,假设一个小端架构,其中 long long 和 long double 都使用 64 位:

But here is my code to demonstrate the problem, assuming a little endian architecture, where both long long and long double use 64 bits:

#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 位):

The output is (using MSVC 2008 32bits on a old XP 32 box):

43ee000000000000
f000000000000000
1.72938e+019
8000000000000000

值的解释:

  • 0xf000000000000000 是十进制的 17293822569102704640,因此转换为 double 是正确的.
  • 43ee000000000000 :尾数部分是 e000000000000 添加隐含的 1 它正确表示 4 位 1 后跟 0 - 去除 3ff 偏差后指数为 43e 它给出一个二进制1.111 263 的表示,因此 0xf000000000000000 或 17293822569102704640 的确切表示(ref)
  • 0xf000000000000000 is 17293822569102704640 in decimal, so the conversion to double is correct.
  • 43ee000000000000 : mantissa part is e000000000000 adding the implied 1 it correctly represents 4 bits with 1 followed with 0 - exponent is 43e after removing the 3ff bias it gives a binary representation of 1.111 263 so the exact representation of 0xf000000000000000 or 17293822569102704640 (ref)

由于该值可以表示为 unsigned long long,我预计它转换为 unsigned long long 会给出原始值,而 MSVC 给出 0x8000000000000000 或 9223372036854775808

As that value can be represented as an unsigned long long, I expected that its conversion to an unsigned long long gives original value, and MSVC gives 0x8000000000000000 or 9223372036854775808

问题是:转换是由另一个问题的公认答案所建议的未定义行为引起的,还是真的是 MSVC 错误?

The question is: is that conversion caused by undefined behaviour as suggested by the accepted answer to the other question or is it really a MSVC bug?

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

(Note: same code on CLang compiler on a FreeBSD 10.1 box gives correct results)

作为参考,我可以找到生成的代码:

For references, I could find the generated code:

  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 的代码似乎是(在执行时从调试器获得):

And the code for _ftol2 seems to be (got from debugger at execution time):

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 

推荐答案

这主要是对问题的评论汇总.

This is mainly a compilation of comments to question.

旧的 MSVC 版本似乎用于错误地处理 64 位整数到 64 位双精度数的转换.

It appears that old MSVC versions used to incorrectly process conversions of 64 bits integers to 64 bits double precision number.

2008以下版本存在的bug.

The bug in present in versions below 2008.

MSCV 2010 使用 32 位模式是错误的,在 64 位模式下正确

MSCV 2010 is wrong using 32 bits mode and correct in 64 bits mode

从 2012 开始的所有版本都是正确的.

All versions starting with 2012 are correct.

这篇关于不正确的双长转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆