std :: reduce的浮点数有多安全?

时间:2016-05-05 04:29:16

标签: c++ floating-point c++17

此示例代码取自std::reduce

#include <iostream>
#include <chrono>
#include <vector>
#include <numeric>
#include <execution_policy>

int main()
{
    std::vector<double> v(10'000'007, 0.5);

    {
        auto t1 = std::chrono::high_resolution_clock::now();
        double result = std::accumulate(v.begin(), v.end(), 0.0);
        auto t2 = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double, std::milli> ms = t2 - t1;
        std::cout << std::fixed << "std::accumulate result " << result
                  << " took " << ms.count() << " ms\n";
    }

    {
        auto t1 = std::chrono::high_resolution_clock::now();
        double result = std::reduce(std::par, v.begin(), v.end());
        auto t2 = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double, std::milli> ms = t2 - t1;
        std::cout << "std::reduce result "
                  << result << " took " << ms.count() << " ms\n";
    }
}

对于那些不熟悉的人,std::reducestd::accumulate相似,除了:

  

范围的元素可以被任意分组和重新排列   为了

std::par表示:

  

在同一个线程中执行的任何此类调用都是不确定的   相对于彼此排序。

std::reduce的页面进一步说:

  

如果binary_op不是关联的,则行为是不确定的   不可交换。

给出的示例输出是:

std::accumulate result 5000003.50000 took 12.7365 ms
std::reduce result 5000003.50000 took 5.06423 ms

AFAIK,Clang或GCC都没有实现图书馆TS,所以样本可能已经组成,但这不是问题的关键。

鉴于浮点的属性,这段代码是“安全的”吗? std::reduce实际上会产生与std::accumulate完全相同的结果,甚至是多次测试吗?

这里的假设是操作纯粹是加法,而不是乘法。

2 个答案:

答案 0 :(得分:4)

应用于浮点类型时,std::plus不是关联的。 (a + b) + c可能与a + (b + c)

不同

鉴于此,我会说出你的问题的答案

  

std::reduce实际上会产生与std::accumulate完全相同的结果,甚至是多次测试吗?

是:

结果可能不同。它们可能相同,但不能保证。

答案 1 :(得分:2)

IEEE double有53个有效位,约为1E16左右。

10'000'007有10位数字。

这意味着IEEE double可以 完全表示步长为10'000'007/2的0到0.5的每个值。

double添加是确定性的,除非经常你最终会得到&#34;逻辑&#34;不能表示为double的值,此时会发生舍入。由于没有添加10'000'007元素的子序列可能会导致double无法完美表示的数字,因此结果具有确定性且已知。

double的通用向量执行此操作不会成立。一个简单的例子是向示例向量添加大约1E20的值和-1E20左右的另一个值。添加0.5中的任何一个都不会做任何事情;当他们相互加入时,他们会取消。因此结果将是05000003.50000

之间的任何位置(在整数或半整数上)