在极限情况下,在x86架构上测试简单的算术运算时,我发现了一个令人惊讶的行为:
const double max = 9.9e307; // Near std::numeric_limits<double>::max()
const double init[] = { max, max, max };
const valarray<double> myvalarray(init, 3);
const double mysum = myvalarray.sum();
cout << "Sum is " << mysum << endl; // Sum is 1.#INF
const double myavg1 = mysum/myvalarray.size();
cout << "Average (1) is " << myavg1 << endl; // Average (1) is 1.#INF
const double myavg2 = myvalarray.sum()/myvalarray.size();
cout << "Average (2) is " << myavg2 << endl; // Average (2) is 9.9e+307
(在发布模式下使用MSVC测试,以及通过Codepad.org测试gcc.MSVC的调试模式将平均值(2)设置为#INF
。)
我希望平均值(2)等于平均值(1),但在我看来,C ++内置除法运算符已被编译器优化,并以某种方式阻止累积达到#INF
。 />
简而言之:大数字的平均值不会产生#INF
。
我在MSVC上观察到与std算法相同的行为:
const double mysum = accumulate(init, init+3, 0.);
cout << "Sum is " << mysum << endl; // Sum is 1.#INF
const double myavg1 = mysum/static_cast<size_t>(3);
cout << "Average (1) is " << myavg1 << endl; // Average (1) is 1.#INF
const double myavg2 = accumulate(init, init+3, 0.)/static_cast<size_t>(3);
cout << "Average (2) is " << myavg2 << endl; // Average (2) is 9.9e+307
(但这次,gcc将平均值(2)设置为#INF
:http://codepad.org/C5CTEYHj。)
由于
答案 0 :(得分:4)
只是猜测,但是:可能是平均值(2)直接在浮点寄存器中计算,浮点寄存器的宽度为80位,溢出时间晚于64位存储器,用于存储器中的双精度数。您应该检查代码的反汇编,看看是否确实如此。
答案 1 :(得分:2)
这是一种功能,或者至少是故意的。 基本上,x86上的浮点寄存器有更多 精度和范围比双倍(15位指数,而不是 11,64位matissa,而不是52)。 C ++标准允许 使用更高的精度和中间值范围,和 几乎所有英特尔的编译器都会这样做 情况;性能差异很大。 是否获得扩展精度取决于何时 以及编译器是否溢出到内存。 (保存结果 命名变量需要编译器将其转换为实际 双精度,至少按照标准。) 更糟糕的是,我见过的是一些基本上做过的代码:
return p1->average() < p2->average()
,average()
在内部表上执行您期望的操作
在数据中。在某些情况下,p1
和p2
实际上会指出
到同一个元素,但返回值仍然是真的;
其中一个函数调用的结果将溢出
内存(并截断为double
),另一个的结果
留在浮点记录中。
(该函数被用作sort
的排序函数,
由于这种影响,结果代码崩溃了
没有定义足够严格的订购标准,而且
传递给它的范围之外的sort
代码。)
答案 2 :(得分:1)
在某些情况下,编译器可以使用比声明类型隐含的更宽泛的类型,但是AFAIK,这不是其中之一。
因此我认为我们的效果类似于Gcc bug 323的效果,但不应该使用额外的精度。
x86有80位FP内部寄存器。虽然gcc倾向于以最大精度使用它们(因此bug 323),但我的理解是MSVC将精度设置为53位,64位的精度加倍。但加长显着性不是80位FP的唯一差异,指数范围也增加。和IIRC一样,x86中没有设置强制使用64位双倍范围。
现在,键盘似乎无法访问,或者我在没有80位长的双重架构上测试代码。
答案 3 :(得分:1)
g++ -O0 -g -S test.cpp -o test.s0
g++ -O3 -g -S test.cpp -o test.s3
比较test.s [03]表明确实valarray :: sum甚至没有被再次调用。我没有长时间关注它,但以下片段似乎是定义的片段:
.loc 3 16 0 ; test.s0
leal -72(%ebp), %eax
movl %eax, (%esp)
call __ZNKSt8valarrayIdE3sumEv
fstpl -96(%ebp)
leal -72(%ebp), %eax
movl %eax, (%esp)
call __ZNKSt8valarrayIdE4sizeEv
movl $0, %edx
pushl %edx
pushl %eax
fildq (%esp)
leal 8(%esp), %esp
fdivrl -96(%ebp)
fstpl -24(%ebp)
.loc 3 17 0
与
.loc 1 16 0 ; test.s3
faddl 16(%eax)
fdivs LC3
fstpl -336(%ebp)
LVL6:
LBB449:
LBB450:
.loc 4 514 0