奇怪的浮点行为与没有额外变量,为什么?

Meh*_*dad 8 c++ floating-point double unsigned-long-long-int visual-c++

当我在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;
}
Run Code Online (Sandbox Code Playgroud)

我明白了

x  == 9.7109089990089994e+018
y1 == 9710908999008999424
y2 == 9223372036854775808
Run Code Online (Sandbox Code Playgroud)

在调试器中.

我很头脑.可有人请向我解释到底如何y1,并y2有不同的价值观?


更新:

这似乎只发生在/Arch:SSE2or /Arch:AVX,not /Arch:IA32/Arch:SSE.

eca*_*mur 5

92233720368547758080x8000000000000000;也就是说,它等于INT64_MINcast to uint64_t

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

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

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

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

  • 为什么编译器会将 `std::floor(9710908999.0089989 * 1.0E9)` 转换为 `long long`,然后转换为 `unsigned long long`,而不是将 `x` 转换为 `long long`,然后再转换为 `unsigned long long`? (2认同)

小智 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();
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

然而,无论它的价值如何,这似乎都不是故意的,因此即使标准允许这种行为,仍然值得将其报告为错误。