繁体   English   中英

2 个向量 C++ 的点积

[英]Dot product of 2 vectors C++

我必须编写输出两个向量的点积的程序。

仅使用 Double 类型组织计算以获得尽可能准确的结果。

输入应如下所示:

 N - vector length x1, x2,..., xN co-ordinates of vector x (double type) y1, y2,..., yN co-ordinates of vector y (double type)

输入样本:

 4 1.0e20 -1.0e3 0.1 1.0e20 1.0 4.0 -4.0 -1.0

以上向量的输出:

 -4000.4

还有我的代码(我还没有使用 cin 因为起初我想用示例输入编写工作程序):

#include <iostream>
#include <numeric>
#include <vector>
#include <functional>

int main(){
//double N; //length of both vectors , will be used when I will have to input vectors by cin
//std::cin >> N;
//N = 4;

std::vector<double> x{1.0e20, -1.0e3, 0.1, 1.0e20};
std::vector<double> y{1.0, 4.0, -4.0, -1.0};
double result = std::inner_product(x.begin(), x.end(), y.begin(), 0);

std::cout << result;
return 0;
}

我的输出是 -2.14748e+09 所以它甚至不接近预期的输出。 我应该怎么做才能使它工作?

(第一)问题

这是<numeric>内积的函数模板:

template <class InputIterator1, class InputIterator2, class T>
   T inner_product (InputIterator1 first1, InputIterator1 last1,
                    InputIterator2 first2, T init);

请注意,定义输出类型T的是init参数。 因此,鉴于您的输入:

std::inner_product(x.begin(), x.end(), y.begin(), 0);

init = 0 ,因此类型Tint 因此,当算法运行时,它会将double值类型转换为int ,最终将返回一个未定义的int值。

“修复”和第二个问题

要解决这个问题,您所要做的就是提供一个正确类型的init值(即,提供一个double作为init参数)。 只需0.0

std::inner_product(x.begin(), x.end(), y.begin(), 0.0);

现在,当您使用该修复程序编译并运行程序时,它仍然会输出错误的结果0

这是因为当inner_product函数累积值时,它使用标准double加法来完成。 因此,您会受到标准double精度误差的影响,它的机器 epsilon为 2^(-52) — 2.22E-16 或大约小数点后十六位的误差 — 这意味着,对于数字 1E20,(1E20 + x ) = 1E20 对于所有 x < 2^(-52)*1E20 ≈ 22204.46。

为了说明这一点,让我们在python解释器中添加1E20 + 23000 (提醒python使用IEEE-754浮点运算,它等于标准C++编译器中double的精度):

>>> 1e20 + 23000
1.0000000000000002e+20

因此,您会看到添加中忽略/“吸收”了少于两万的任何内容。

由于您的其他数字小于 22204.46,因此 1e20 只会“吸收”它们,直到将其添加到 -1E20 中,然后“取消”并返回0

(简单)修复

解决第二个问题的最简单方法是使用long double而不是double 这种更精确的双精度类型的机器 epsilon 为 2^(-63) — 1.08E-19 或大约 19 个小数位 — 这意味着,对于您的输入 1E20,不精确将等于 2^(-63) *1E20,或约 10.84。 运行程序,输出将是-4000 ,这与预期的答案非常接近。 但这可能不是您的教授所期望的,因为他特别要求输出恰好是-4000.4

注意:显然,您可以使用另一种更精确的数字类型,但您的教授可能希望您使用double ,因此我不会详细介绍。

编辑:正如评论中提到的@phuclv一些编译器没有将long double实现为 80 位浮点值,而是可能具有与double (64 位)相同的精度。 因此,您可能需要寻找提供适当的 80 位精度long double s 甚至128 位 IEEE-754 四倍精度浮点类型的库。 虽然这绝对不会被认为是“容易的”。

(大部分是正确的)修复

好吧,你不能无限精确,因为double类型有 epsilon = 2^(-52),但是你可以在加法中更聪明,而不仅仅是将大值添加到小值(记住:大值“吸收”小,因为double浮点运算中的不精确)。 基本上,您应该计算一个具有值的成对乘法的数组,然后对其进行排序(基于绝对值),然后使用std::accumulate将值相加:

#include <iostream>
#include <numeric>
#include <vector>
#include <functional>
//Mind the use of these two new STL libraries
#include <algorithm> //std::sort and std::transform
#include <cmath> //abs()



int main(){

    std::vector<double> x{1.0e20, -1.0e3, 0.1, 1.0e20};
    std::vector<double> y{1.0, 4.0, -4.0, -1.0};
    //The vector with the pairwise products
    std::vector<double> products(x.size());

    //Do element-wise multiplication
    //C code: products[i] += x[i] * y[i];
    std::transform(x.begin(), x.end(), y.begin(), products.begin(), std::multiplies<double>());

    //Sort the array based on absolute-value
    auto sort_abs = [] (double a, double b) { return abs(a) < abs(b); };
    std::sort(products.begin(), products.end(), sort_abs);

    //Add the values of the products(note the init=0.0)
    double result = std::accumulate(products.begin(), products.end(), 0.0);

    std::cout << result << std::endl;
    return 0;
}

使用此新代码,结果如预期: -4000.4

坚韧它显然有它的局限性。 例如,如果输入是向量 v1 = {100.0, 1E20} 和 v2 = {10.0, 1.0},结果应该返回100000000000000001000 ,显然只会返回 1E20。

发布的代码段中存在逻辑错误和一些数字问题。

  • std::inner_product使用传递的初始值初始化累加器,因此它对 a 和返回值使用相同的类型。 发布的代码使用整数0 ,而应使用浮点值,如0.0
  • 向量中的值具有极宽的幅度范围。 double这样的浮点类型具有有限的精度,它不能表示没有舍入误差的所有可能的实数。 此外(并且因此)浮点数学运算对它们的执行顺序不具有关联性和敏感性。

要对其进行描绘,您可以运行以下代码段。

#include <numeric>
#include <algorithm>
#include <array>
#include <fmt/core.h> // fmt::print

int main()
{
    using vec4d = std::array<double, 4>;
    
    vec4d x{1.0e20, 1.0e20, -1.0e3, 0.1};
    vec4d y{1.0, -1.0, 4.0, -4.0};
    
    vec4d z;
    std::transform( std::begin(x), std::end(x), std::begin(y), std::begin(z)
                  , std::multiplies<double>{} );
    std::sort(std::begin(z), std::end(z));

    fmt::print("{0:>{1}}\n", "sum", 44);
    fmt::print("{0:->{1}}", '\n', 48);
    do {
        for (auto i : z) {
            fmt::print("{0:8}", i);
        }
        auto sum{ std::accumulate(std::begin(z), std::end(z), 0.0) };
        fmt::print("{0:{1}.{2}f}\n", sum, 14, 1);
    } while ( std::next_permutation(std::begin(z), std::end(z)) );
}

这是它的输出:

                                         sum
-----------------------------------------------
  -1e+20   -4000    -0.4   1e+20           0.0
  -1e+20   -4000   1e+20    -0.4          -0.4
  -1e+20    -0.4   -4000   1e+20           0.0
  -1e+20    -0.4   1e+20   -4000       -4000.0
  -1e+20   1e+20   -4000    -0.4       -4000.4
  -1e+20   1e+20    -0.4   -4000       -4000.4
   -4000  -1e+20    -0.4   1e+20           0.0
   -4000  -1e+20   1e+20    -0.4          -0.4
   -4000    -0.4  -1e+20   1e+20           0.0
   -4000    -0.4   1e+20  -1e+20           0.0
   -4000   1e+20  -1e+20    -0.4          -0.4
   -4000   1e+20    -0.4  -1e+20           0.0
    -0.4  -1e+20   -4000   1e+20           0.0
    -0.4  -1e+20   1e+20   -4000       -4000.0
    -0.4   -4000  -1e+20   1e+20           0.0
    -0.4   -4000   1e+20  -1e+20           0.0
    -0.4   1e+20  -1e+20   -4000       -4000.0
    -0.4   1e+20   -4000  -1e+20           0.0
   1e+20  -1e+20   -4000    -0.4       -4000.4
   1e+20  -1e+20    -0.4   -4000       -4000.4
   1e+20   -4000  -1e+20    -0.4          -0.4
   1e+20   -4000    -0.4  -1e+20           0.0
   1e+20    -0.4  -1e+20   -4000       -4000.0
   1e+20    -0.4   -4000  -1e+20           0.0

请注意,“正确”答案 -4000.4 仅在较大项(1e+20 和 -1e+20)在第一次求和中抵消时才会出现。 由于选择了特定的数字作为输入,这是一个伪像,其中两个最大的数字在数量上相等并且符号相反。 一般来说,减去几乎是一些的两个数字会导致灾难性的取消和意义的丧失。

次佳结果 -4000.0,发生在以量级而言较小的值 0.4“接近”最大的值并被抵消时。

在对许多项求和时,可以采用各种技术来减少不断增长的数值误差量,例如成对求和或补偿求和(参见例如Kahan 求和)。

在这里,我用相同的样本测试了 Neumaier 求和。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM