如何在C ++中有效地计算2 ^ y = 2 ^ q0 + ... + 2 ^ qn中的'y'?

时间:2013-12-13 08:34:06

标签: c++ math

我有等式:

2^y = 2^q0 + ... 2^qn

n是任意整数(有任意数量的'q')。 'q'的值可以大到500,其中2 ^ q无法存储在整数或长变量类型中。我想计算“y”而不是下面的方法,因为存储容量问题:

log2(2^q0 + ... + 2^qn)

如何在C ++中以有效的方式计算'y'。无论如何,在'q'上用简单的算术运算来计算'y'?

编辑:'q是非负的,我找这个问题的2个版本:'q是整数,'q是双

5 个答案:

答案 0 :(得分:13)

首先排序qi。假设最小为p,从所有p中减去qi。你可以检查qi是否形成算术系列,如果你很幸运并且他们形成了这样的系列,你有一个数学捷径,但是否则因为AFAIK没有数学规则来简化log(a1 + a2 + ... + ak)计算y的最佳方法是:

由于您已对qi进行了排序,因此您可以以类似动态算法的方式计算sum = 1 + 2 ^ (q1-p) + 2 ^ (q2-p) + ...(即使用以前的结果来计算下一个术语)。

prev_exp = 0;
prev_term = 1;
this_term = 0;
sum = 1;
// p is previously subtracted from q[i]s
for (int i = 1; i < n; ++i) {
  this_term = prev_term * (1 << (q[i] - prev_exp)); // 2 ^ m = (1 << m)
  prev_term = this_term;
  prev_exp = q[i] - prev_exp;
  sum += this_term;
}

y可以计算为y = p + log2(sum)

请注意,您还要先对小数字求和。这将有助于浮点精度。

我正在编辑这个答案,根据分而治之的算法类型添加另一个解决方案,但我无法完成它,但我猜我是否将它留在隐藏的块中(此站点的编辑器命名中的扰流块)有人可以完成或改进这部分答案。随时编辑。

  

如果q[i] s的最大值大于它们的最小值(即  p),你可以使用分而治之算法,递归计算  sum1 = 1 + 2^(q[1]-p) + .... + 2^(q[n/2]-p)sum2 = 2^(q[n/2 + 1]-p) + ... + 2 ^ (q[n-1] - p)您可以分解2^(q[n/2 + 1]-p)  这里也。然后你会有:    y = p + log2(sum1 + sum2) = p + log2(sum1 + 2^p' sum2')其中p'  是q[n/2 + 1]-p。它可以帮助您减少数字。

答案 1 :(得分:1)

这显然是标准或内置类型无法提供的问题。

您可以观察到,2 ^ qx是左侧1个移位的qx位,log2(y)是在数字变为1之前必须采取的右移位数。 然后你可以观察到在溢出中添加两个数字会使结果小于addns,并且在左边传播一个数字。

此时你可以:

  • 实现一个存储500位的类(您可以使用unsigned x[1+500/sizef(unsigned)])
  • 给该类一个显式构造函数,使用unsigned q设置适当的位(将q除以sizeof(无符号)以确定索引,并使用modulous来确定剩余的移位)
  • 为你的“大整数”实现+运算符(只需对从最后一个开始的子整数求和并传播进位,如果有的话 - 即:如果总和低于加数)
  • 通过计算整体最高位置1来实现log2操作。

如果你能确保各种qi不重复,就不需要这样的算术:你要做的只是记住最高的那个。

答案 2 :(得分:0)

你不需要取幂或乘以2的整数幂:只需将1移动适当的量。我认为以下代码效率最高:

double ComputeY( vector<unsigned> Qs )
{
    unsigned sum = 0;
    for( vector<unsigned>::iterator it = Qs.begin(); it != Qs.end(); ++it )
    {
        int q = *it; // Get the next q
        int two_power_q = 1 << q; // This is the key: 2^q == 1 << q
        sum += two_power_q;
    }

    double y = log2( sum ); // need double because the result may not be integer
    return y; 
}

答案 3 :(得分:0)

此解决方案类似于MJafar Mash的解决方案,但避免无穷大(例如:如果相邻的指数有巨大差异{0,1010})。

#include <algorithm>
#include <cmath>
#include <iostream>
#include <limits>
#include <vector>
#include <iostream>
#include <type_traits>

template <typename T>
double logarithm(const std::vector<T>& sorted_exponents) {
    typedef typename std::make_unsigned<T>::type Unsigned;
    const unsigned digits = 32;
    // Evaluate sum(2^sorted_exponents[i]) = sum * 2^scale*digits
    double sum = 0;
    Unsigned scale = 0;
    for(Unsigned e : sorted_exponents) {
        // Evaluate e = 2^e * 2^(s*digits)
        Unsigned s = e / digits;
        e %= digits;
        sum = double(uint32_t(1) << e) + sum / pow(2.0, double(s - scale)*digits);
        scale = s;
    }
    return std::log2(sum) + double(scale)*digits;
}

int main()
{
    std::cout.precision(std::numeric_limits<double>::digits10 + 1);
    std::vector<int> v;
    unsigned size = 500;
    for(unsigned max_exponent = 1; max_exponent < 10000000; max_exponent *= 10) {
        v.clear();
        v.reserve(size);
        for(unsigned i = 0; i < size; ++i)
            v.push_back(std::rand() % max_exponent);
        std::sort(v.begin(), v.end());
        double log = logarithm(v);
        std::cout << "Maximal Exponent: " << v.back() << std::endl;
        std::cout << "       Logarithm: " << log;
        std::cout <<  std::endl;
    }
}

结果:

Maximal Exponent: 0
       Logarithm: 8.965784284662087
Maximal Exponent: 9
       Logarithm: 15.58590144969077
Maximal Exponent: 99
       Logarithm: 102.4678312951325
Maximal Exponent: 997
       Logarithm: 997.5899323737988
Maximal Exponent: 9999
       Logarithm: 9999.000000002708
Maximal Exponent: 99960
       Logarithm: 99960.32192809488
Maximal Exponent: 998055
       Logarithm: 998055

答案 4 :(得分:0)

让我们消化这里的问题。

  qn   binary
2^0 => 00000001
2^1 => 00000010
2^2 => 00000100
---------------
sum => 00000111
y => log2(sum) => 2

这意味着在总结所有2 ^ q0 + ... 2 ^ qn后,它询问最左边的二进制1的位置在哪里 和qn是每个2 ^ qn中二进制数字1的位置。

其他人已经指出,如果q中的所有都是唯一的,则y = max_element(q)。

如果不是这种情况,我们必须计算总和并找到最左边的二进制数字1。 要计算总和,我们不需要保留所有数字,只保留最左边的数字。

让我们将每个2 ^ qn转换为二进制,并将q中的每个元素按数字1所在的位置分组。 如果数字1落入前48位,则将其置于组0中,如果它落入第二位48位,则将其置于组1中,依此类推。 然后我们计算组0的总和并将所有溢出数字转发到下一组,直到我们计算最后一组。

选择48位数组,以便可以使用int64计算,8位MSB位为carrie forward。
在最后一组中,找到最左边数字1的位置: left_most_bit 然后 y = left_most_bit + group_number * 48

的值
int function(vector<int> const& q)
{
    vector<int> group;

    // group each element in q
    for(int i=0; i<q.size(); ++i)
    {
        group[i] = q[i] / 48;
    }

    // find the maximum group
    int group_max = std::max_element(group.begin(), group.end());
    int64 sum = 0;

    for(int g=0; g<=group_max; ++g)
    {
        // calculate group of 48 bits
        for(int i=0; i<q.size(); ++i)
        {
            if(group[i] == g)  // is this elementin the group that we are calculating?
            {
                int pos_of_digit_1 = q[i] - g * 48;
                sum += (1 << pos_of_digit_1);   // convert to value and sum it.
            }
        }
        sum = sum >> 48;    // carrie forward the 8 MSB bits
    }

    int pos_of_left_most_1 = 0;
    for(int i=0; i<64; ++i)
    {
        if((sum << i) & 0x8000000000000000);
        {
            pos_of_left_most_1 = 64 - i;
            break;
        }
    }

    return group_max * 48 + pos_of_left_most_1;
}

这个答案是针对整数值y和q的。对于浮点值,我们不能这样做。