将双打的阵列汇总为较大的价值跨度:正确的算法

summing array of doubles with large value span : proper algorithm

本文关键字:算法 阵列      更新时间:2023-10-16

我有一个算法,我需要总和(很多时间)double数字在e-40到e 40。

数组示例(从实际应用程序随机倾倒):

-2.06991e-05 
7.58132e-06 
-3.91367e-06 
7.38921e-07 
-5.33143e-09
-4.13195e-11 
4.01724e-14 
6.03221e-17 
-4.4202e-20
6.58873 
-1.22257
-0.0606178 
0.00036508 
2.67599e-07 
0
-627.061
-59.048 
5.92985 
0.0885884
0.000276455 
-2.02579e-07

不用说,我知道这将导致的舍入效果,我试图控制它:最终结果不应在双重的部分中没有任何丢失的信息应该至少是n位准确的(定义为n个)。最终结果需要5位数字和指数。

经过一些体面的思考,我最终遵循算法:

  • 对数组进行排序,以使最大的绝对值首先出现,最接近零。
  • 将所有内容添加到循环中

的想法是,在这种情况下,任何取消大价值(负和正值)都不会影响后者较小的值。简而言之:

  • (10E40-10E40) 1 = 1:结果是预期
  • (1 10E -40)-10E40 = 0:不好

我最终使用了std :: multiSet(与普通双打相比,PC上的基准的速度高20%,而我可以使用std:Fabs的自定义排序功能。

它仍然很慢(完成整个事情需要5秒钟),我仍然有这种感觉"您错过了算盘中的东西"。任何推荐:

  1. 用于速度优化。有没有更好的方法来对中间产品进行分类?排序一组40个中间结果(通常)约为总执行时间的70%。
  2. 对于错过的问题。是否有机会仍然丢失关键数据(最终结果的分数应该是一个?
  3. )?

在更大的情况下,我正在实现纯想象变量的真实系数多项式类别(电阻抗:Z(JW))。Z是代表用户定义的系统的大型多个电话,系数指数范围很远。
"大"来自将ZC1 = 1/JC1W之类的东西添加到zc2 = 1/jc2w:
ZC1 ZC2 =(C1C2(JW)^2 0(JW))/(C1 C2)(JW)

在这种情况下,C1C2在Nanofarad(10E-9)中使用C1和C2,已经在10E-18中(并且仅启动...)

我的排序功能使用复杂变量的曼哈顿距离(因为我的纯真实或纯虚构):

struct manhattan_complex_distance
{
        bool operator() (std::complex<long double> a, std::complex<long double> b)
        {
            return std::fabs(std::real(a) + std::imag(a)) > std::fabs(std::real(b) + std::imag(b));
        }
};

和我的多集在行动中:

std:complex<long double> get_value(std::vector<std::complex<long double>>& frequency_vector)
{
    //frequency_vector is precalculated once for all to have at index n the value (jw)^n. 
    std::multiset<std::complex<long double>, manhattan_distance> temp_list;   
    for (int i=0; i<m_coeficients.size(); ++i)
    {
        //   element of :       ℝ         *         ℂ
        temp_list.insert(m_coeficients[i] * frequency_vector[i]);
    }
    std::complex<long double> ret=0;
    for (auto i:temp_list)
    {
        // it is VERY important to start adding the big values before adding the small ones.
        // in informatics, 10^60 - 10^60 + 1 = 1; while 1 + 10^60 - 10^60 = 0. Of course you'd expected to get 1, not 0.
        ret += i;
    }
    return ret;
}

我拥有的项目是启用C 11(主要用于改进数学和复杂数字工具)

ps:我重新制作要制作的代码很容易读取,实际上,所有复合物和长的双人名称都是模板:我可以立即更改多项式类型,或者将类用于ℝ

<的常规多项式>

正如Guygreer所建议的那样,您可以使用Kahan总结:

double sum = 0.0;
double c = 0.0;
for (double value : values) {
    double y = value - c;
    double t = sum + y;
    c = (t - sum) - y;
    sum = t;
}

编辑:您还应该考虑使用霍纳的方法评估多项式。

double value = coeffs[degree];
for (auto i = degree; i-- > 0;) {
    value *= x;
    value += coeffs[i];
}

对数据进行排序在正确的轨道上。但是,您绝对应该从最小到最大的总结,而不是从最大到最小的。从最大到最小的总结,到达最小的时候,将下一个值与当前总和对齐时,可能会导致下一个值的大部分或全部位数"掉下来"。最小的值从最小到最大,可以将更多的款项累积到最大的位置。结合Kahan总和,应该产生相当准确的总和。

首先:让您的数学跟踪错误。将双打替换为错误感知类型,当您添加或乘以两个双打时,它也会计算 maximim error

这是您保证代码在合理快速的同时产生准确结果的唯一方法。

第二,不要使用multiset。关联容器不是用于排序的,而是用于维护排序集合,同时能够有效地添加或删除元素。

添加/删除元素的能力增量意味着基于节点,而基于节点的表示通常是缓慢的。

如果您只需要分类的集合,请从vector开始,然后std::sort

接下来,要最大程度地减少错误,请保留正面和负元素的列表。从零开始作为您的总和。现在选择最小的正或负元素中的最小元素,以使您的总和最接近零。

使用计算其误差界限的元素。

最后,确定您是否有5个精度。

这些错误的双打应尽可能在算法中尽早使用。