不正确的双长转换 [英] Incorrect double to long conversion
问题描述
这主要是对另一个问题的跟进,这是关于一个奇怪的转换从 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 with0
- 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屋!