// KMB 2003 Dec 04
//     2005 Sep 12 - 64-bit clean
// mcopy -o rng-kmb.c a:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>

double gaussdensity(double x,double m, double s) {
  const double root2pi=2.506628274631000502415765284;
  return exp(-0.5*(x-m)*(x-m)/s/s)/s/root2pi;
}

uint32_t xcng(void);
double uni(void);
double normal01(void);

uint32_t xcng(void) {
  static uint32_t _x=123456789,_y=521289;
  static uint32_t z,w;
  z=_x+_y; _x=_y; w=(z<<8); _y=w+17*(z>>24);
  if (z<_x) _y+=4352; if (_y<w) _y+=17;
  return _y;
}

double uni(void) {
  return 2.3283064365386962891e-10*(double)xcng(); /* 1.0/2^32 */
}

uint32_t uni_int(uint32_t n) {
  return (uint32_t)floor(n*2.3283064365386962891e-10*xcng());
}

double normal01() {
  static unsigned int iset=1;
  static double gset;
  double v1,v2,r,fac;
  if (iset) {
    do {
      v1=4.656612873077392578125e-10*(double)(xcng())-1.0;
      v2=4.656612873077392578125e-10*(double)(xcng())-1.0;
      r=v1*v1+v2*v2;
    } while (r>=1.0);
    fac=sqrt(-2.0*log(r)/r);
    gset=v1*fac;
    iset=0;
    return v2*fac;
  } else {
    iset=1;
    return gset;
  }
}

double chi2density(unsigned int df, double w) {
  const double log2=0.6931471805599453094172321214;
  double df2;
  if (w<0.0) { fprintf(stderr,"w<0 in chi2density\n"); exit(1); }
  if (w==0.0) {
    if (df==1) return 0.0/0.0;
    if (df==2) return 0.5;
    return 0.0;
  }
  df2=df/2.0;
  return exp(-w/2+(df2-1)*log(w)-df2*log2-lgamma(df2));
}

double noncentral_chi2density(unsigned int df, double w, double lambda) {
  double s=0.0,p=1.0,t,l2=0.5*lambda;
  unsigned int r;
  if (lambda==0.0) return chi2density(df,w);
  if (df==1 && w==0.0) return 0.0/0.0;
  for (r=0; r<1000; r++) {
    t=chi2density(df+2*r,w);
    s+=p*t;
    //fprintf(stderr,"%f %f\n",s,t);
    if (fabs(t)<1e-6*(1+fabs(s))) return exp(-l2)*s;
    p*=l2/(r+1);
  }
  fprintf(stderr,"no convergence in noncentral_chi2density\n");
  exit(1);
}
