Efficient way to compute geometric mean of many numbers

前端 未结 7 1069
栀梦
栀梦 2020-12-14 07:12

I need to compute the geometric mean of a large set of numbers, whose values are not a priori limited. The naive way would be

double geometric_mean(std::vect         


        
7条回答
  •  情深已故
    2020-12-14 07:29

    The "split exponent and mantissa" solution:

    double geometric_mean(std::vector const & data)
    {
        double m = 1.0;
        long long ex = 0;
        double invN = 1.0 / data.size();
    
        for (double x : data)
        {
            int i;
            double f1 = std::frexp(x,&i);
            m*=f1;
            ex+=i;
        }
    
        return std::pow( std::numeric_limits::radix,ex * invN) * std::pow(m,invN);
    }
    

    If you are concerned that ex might overflow you can define it as a double instead of a long long, and multiply by invN at every step, but you might lose a lot of precision with this approach.

    EDIT For large inputs, we can split the computation in several buckets:

    double geometric_mean(std::vector const & data)
    {
        long long ex = 0;
        auto do_bucket = [&data,&ex](int first,int last) -> double
        {
            double ans = 1.0;
            for ( ;first != last;++first)
            {
                int i;
                ans *= std::frexp(data[first],&i);
                ex+=i;
            }
            return ans;
        };
    
        const int bucket_size = -std::log2( std::numeric_limits::min() );
        std::size_t buckets = data.size() / bucket_size;
    
        double invN = 1.0 / data.size();
        double m = 1.0;
    
        for (std::size_t i = 0;i < buckets;++i)
            m *= std::pow( do_bucket(i * bucket_size,(i+1) * bucket_size),invN );
    
        m*= std::pow( do_bucket( buckets * bucket_size, data.size() ),invN );
    
        return std::pow( std::numeric_limits::radix,ex * invN ) * m;
    }
    

提交回复
热议问题