8

当我在 VC++ 2013(32 位,无优化)中运行以下代码时:

#include <cmath>
#include <iostream>
#include <limits>

double mulpow10(double const value, int const pow10)
{
    static double const table[] =
    {
        1E+000, 1E+001, 1E+002, 1E+003, 1E+004, 1E+005, 1E+006, 1E+007,
        1E+008, 1E+009, 1E+010, 1E+011, 1E+012, 1E+013, 1E+014, 1E+015,
        1E+016, 1E+017, 1E+018, 1E+019,
    };
    return pow10 < 0 ? value / table[-pow10] : value * table[+pow10];
}

int main(void)
{
    double d = 9710908999.008999;
    int j_max = std::numeric_limits<double>::max_digits10;
    while (j_max > 0 && (
        static_cast<double>(
            static_cast<unsigned long long>(
                mulpow10(d, j_max))) != mulpow10(d, j_max)))
    {
        --j_max;
    }
    double x = std::floor(d * 1.0E9);
    unsigned long long y1 = x;
    unsigned long long y2 = std::floor(d * 1.0E9);
    std::cout
        << "x == " << x << std::endl
        << "y1 == " << y1 << std::endl
        << "y2 == " << y2 << std::endl;
}

我明白了

x  == 9.7109089990089994e+018
y1 == 9710908999008999424
y2 == 9223372036854775808

在调试器中。

我疯了。有人可以向我解释一下到底y1y2什么不同的价值观吗?


更新:

这似乎只发生在/Arch:SSE2or下/Arch:AVX,而不是/Arch:IA32or /Arch:SSE

4

4 回答 4

5

92233720368547758080x8000000000000000;也就是说,它等于INT64_MIN强制转换为uint64_t

看起来您的编译器正在转换 to 的返回值,floor然后long long将该结果转换为unsigned long long.

请注意,浮点到整数转换中的溢出通常会产生最少可表示的值(例如cvttsd2siq在 x86-64 上):

当转换不准确时,将返回截断的结果。如果转换结果大于最大有符号双字整数,则引发浮点无效异常,如果此异常被屏蔽,则返回不定整数值 (80000000H)。

(这来自双字文档,但四字行为是相同的。)

于 2014-01-31T11:53:51.993 回答
5

您正在将超出范围的double值转换为unsigned long long. 这在标准 C++ 中是不允许的,而 Visual C++ 在 SSE2 模式下似乎对它处理得非常糟糕:它在 FPU 堆栈上留下了一个数字,最终溢出它并使得使用 FPU 的后续代码以非常有趣的方式失败。

减少的样本是

double d = 1E20;
unsigned long long ull[] = { d, d, d, d, d, d, d, d };
if (floor(d) != floor(d)) abort();

如果ull有八个或更多元素,则中止,但如果最多有七个,则通过。

解决方案是不要将浮点值转换为整数类型,除非您知道该值在范围内。

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

浮点类型的纯右值可以转换为整数类型的纯右值。转换截断;也就是说,小数部分被丢弃。如果截断的值不能在目标类型中表示,则行为未定义。[注意:如果目标类型是bool,请参见 4.12。——尾注]

超出范围的值在转换为无符号类型时换行的规则仅适用于该值已经是某个整数类型的情况。

然而,不管它值多少钱,这似乎不是故意的,所以即使标准允许这种行为,仍然值得将其报告为错误。

于 2014-01-31T20:00:06.980 回答
2

假设:这是一个错误。编译器正确转换doubleunsigned long long,但将扩展精度浮点(可能long doubleunsigned long long错误地转换为。细节:

double              x = std::floor(9710908999.0089989 * 1.0E9);

这会计算右侧的值并将其存储在x. 右侧的值可能会以扩展精度计算,但按照 C++ 规则的要求,double当存储在x. 确切的数学值将是 9710908999008998870,但将其四舍五入到double格式会产生 9710908999008999424。

unsigned long long y1 = x;

这会将double值转换xunsigned long long,产生预期的 9710908999008999424。

unsigned long long y2 = std::floor(9710908999.0089989 * 1.0E9);

这使用扩展精度计算右侧的值,生成 9710908999008998870。当扩展精度值转换为unsigned long long时,会出现错误,生成 2 63 (9223372036854775808)。该值可能是由将扩展精度格式转换为 64 位整数的指令产生的“超出范围”错误值。编译器使用了不正确的指令序列将其扩展精度格式转换为unsigned long long.

于 2014-01-31T12:08:26.940 回答
0

在再次将其转换为 long 之前,您已将 y1 转换为 double。x 的值不是“地板”值,而是地板的四舍五入值。

同样的逻辑也适用于转换整数和浮点数。float x = (float)((int) 1.5) 将给出与 float x = 1.5 不同的值

于 2014-01-31T11:35:14.640 回答