|
1 | | -// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- |
2 | | -/* :tabSize=4:indentSize=4:noTabs=false:folding=explicit:collapseFolds=1: */ |
| 1 | +// rmultinom.h: Rcpp/Armadillo equivalent to R's stats::rmultinom(). |
3 | 2 | // |
4 | | -// rmultinom.h: Rcpp/Armadillo equivalent to R's stats::rmultinom(). |
5 | 3 | // This is intended for use in C++ functions, and should *not* be called from R. |
6 | 4 | // It should yield identical results to R. |
7 | 5 | // |
8 | | -// Copyright (C) 2014 Christian Gunning |
| 6 | +// Copyright (C) 2014-2026 Christian Gunning |
| 7 | +// Copyright (C) 2026 Dirk Eddelbuettel and R Core |
9 | 8 | // |
10 | 9 | // This file is part of RcppArmadillo. |
11 | 10 | // |
|
29 | 28 | namespace Rcpp{ |
30 | 29 | namespace RcppArmadillo{ |
31 | 30 |
|
32 | | - IntegerVector rmultinom(int size, NumericVector prob) { |
| 31 | + IntegerVector rmultinom(int size, NumericVector prob) { |
33 | 32 | // meaning of n, size, prob as in ?rmultinom |
34 | 33 | // opposite of sample() - n=number of draws |
35 | | - double pp; |
36 | | - int ii; |
37 | 34 | int probsize = prob.size(); |
38 | | - // Return object |
39 | | - IntegerVector draws(probsize); |
40 | | - if (size < 0 || size == NA_INTEGER) throw std::range_error( "Invalid size"); |
41 | | - long double p_tot = 0.; |
42 | | - p_tot = std::accumulate(prob.begin(), prob.end(), p_tot); |
43 | | - if (fabs((double)(p_tot - 1.)) > 1e-7) { |
44 | | - throw std::range_error("Probabilities don't sum to 1, please use FixProb"); |
45 | | - } |
46 | | - |
47 | | - // do as rbinom |
48 | | - if (size == 0 ) { |
49 | | - return draws; |
| 35 | + IntegerVector draws(probsize); // Return object |
| 36 | + |
| 37 | + if (size < 0 || size == NA_INTEGER) |
| 38 | + Rcpp::stop( "Invalid size"); |
| 39 | + |
| 40 | + // Using Kahan compensated summation for platform-independent accuracy |
| 41 | + // avoids relying on LDOUBLE (long double) which varies across platforms. |
| 42 | + // Code borrowed with full credits from R. |
| 43 | + double p_tot = 0.0, |
| 44 | + p_comp = 0.0; // Kahan compensation term |
| 45 | + |
| 46 | + for (int ii = 0; ii < probsize; ii++) { |
| 47 | + double pp = prob[ii]; |
| 48 | + if (!R_FINITE(pp) || pp < 0. || pp > 1.) { |
| 49 | + Rcpp::warning("Domain issue in rmultinom"); |
| 50 | + draws[ii] = NA_INTEGER; |
| 51 | + return draws; |
| 52 | + } |
| 53 | + // Kahan summation: p_tot += pp with compensation |
| 54 | + double y = pp - p_comp, |
| 55 | + t = p_tot + y; |
| 56 | + p_comp = (t - p_tot) - y; |
| 57 | + p_tot = t; |
| 58 | + draws[ii] = 0; |
50 | 59 | } |
51 | | - //rmultinom(size, REAL(prob), k, &INTEGER(ans)[ik]); |
| 60 | + |
| 61 | + if (fabs((double)(p_tot - 1.0)) > 1e-7) |
| 62 | + Rcpp::stop("Probabilities do not sum to 1, please use FixProb"); |
| 63 | + if (probsize == 1 && p_tot == 0.0) // trivial border case: do as rbinom |
| 64 | + return draws; |
| 65 | + if (size == 0 ) // do as rbinom |
| 66 | + return draws; |
| 67 | + |
| 68 | + // rmultinom(size, REAL(prob), k, &INTEGER(ans)[ik]); |
| 69 | + // generate first draws-1 obs via binomials |
52 | 70 | // for each slot |
53 | | - for (ii = 0; ii < probsize-1; ii++) { /* (p_tot, n) are for "remaining binomial" */ |
54 | | - if (prob[ii]) { |
55 | | - pp = prob[ii] / p_tot; |
56 | | - // >= 1; > 1 happens because of rounding |
| 71 | + for (int ii = 0; ii < probsize-1; ii++) { /* (p_tot, n) are for "remaining binomial" */ |
| 72 | + if (prob[ii] != 0.) { |
| 73 | + double pp = prob[ii] / p_tot; |
| 74 | + // >= 1; > 1 happens because of rounding |
57 | 75 | draws[ii] = ((pp < 1.) ? (int) Rf_rbinom((double) size, pp) : size); |
58 | 76 | size -= draws[ii]; |
59 | | - } // else { ret[ii] = 0; } |
| 77 | + } else { |
| 78 | + draws[ii] = 0; |
| 79 | + } |
60 | 80 | // all done |
61 | | - if (size <= 0) return draws; |
62 | | - // i.e. p_tot = sum(prob[(k+1):K]) |
63 | | - p_tot -= prob[ii]; |
| 81 | + if (size <= 0) return draws; |
| 82 | + // Kahan subtraction: p_tot -= prob[k] with compensation |
| 83 | + double y = -prob[ii] - p_comp, |
| 84 | + t = p_tot + y; |
| 85 | + p_comp = (t - p_tot) - y; |
| 86 | + p_tot = t; |
64 | 87 | } |
65 | 88 | // the rest go here |
66 | 89 | draws[probsize-1] = size; |
|
0 commit comments