ベータ分布のquantile

はじめに

boostには、確率分布のquantile(分位数、分布をp:1-pに分割する点)を計算するものが用意されている。
ベータ分布の場合について、自分でも書いてみる。

コード

#include <iostream>
#include <cmath>
//#include <boost/math/distributions/beta.hpp>

class BetaDistribution {
  double eps;
  double val_a, val_b;

  //Ix : regularized incomplete beta function
  double Ix(double x, double a, double b){
    if(a <= 0) return -1;
    if(b <= 0){
      if(x < 1) return 0;
      if(fabs(x-1) < eps) return 1.0;
      return -1;
    }

    if(x > (a+1) / (a+b+2)) return 1 - Ix(1-x, b, a);
    if(x <= 0) return 0;
    double p1 = 0, q1 = 1;
    double p2 = exp(a*log(x) + b*log(1-x) + lgamma(a+b) - lgamma(a) - lgamma(b)) / a, q2 = 1;
    double prev, d;
    for(int k=0; k<500;){
      prev = p2;
      d = -((a+k) * (a+b+k) * x) / ((a+2*k) * (a+2*k+1));
      p1 = p1*d + p2;
      q1 = q1*d + q2;
      k++;
      d = (k * (b-k) * x) / ((a+2*k-1) * (a+2*k));
      p2 = p2*d + p1;
      q2 = q2*d + q1;
      if(fabs(q2) < eps){
        q2 = 1e+9;
        continue;
      }
      p1 /= q2;
      q1 /= q2;
      p2 /= q2;
      q2 = 1;
      if(fabs(p2-prev) < eps) return p2;
    }
    return -1;
  }
public:
  BetaDistribution(double val_a, double val_b):
    val_a(val_a),
    val_b(val_b),
    eps(1e-5) 
  {}
  
  //Int_0^Q { dPhi(x) } >= p
  double quantile(double p){
    double lb = 0.0, ub = 1.0;
    for(int i=0; i<200; i++){
      double m = (lb+ub) / 2.0;
      if(Ix(m, val_a, val_b) < p) lb = m;
      else ub = m;
    }
    return lb;
  }
};

int main(){
  double a, b, p;

  while(std::cin >> a >> b >> p){
    //boost
    //boost::math::beta_distribution<> dist(a, b);
    //std::cout << quantile(dist, p) << std::endl;
    
    //mybeta
    BetaDistribution mybeta(a, b);
    std::cout << mybeta.quantile(p) << std::endl;
  }

  return 0;
}

regularized不完全ベータ関数Ix(a,b)は「C言語による最新アルゴリズム辞典」で紹介されている連分数展開による方法。
quantileの計算は、2分探索で探している・・・
(一応、boostの結果と値はだいたい同じみたいだが、pが0や1に近い点だと誤差が大きくなっている模様)