games_dice 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +69 -5
  3. data/.gitignore +0 -1
  4. data/.rubocop.yml +13 -48
  5. data/CHANGELOG.md +26 -0
  6. data/Gemfile +6 -3
  7. data/README.md +46 -5
  8. data/Rakefile +90 -2
  9. data/ext/games_dice/extconf.rb +26 -0
  10. data/ext/games_dice/games_dice.c +1 -1
  11. data/ext/games_dice/probabilities.c +341 -211
  12. data/ext/games_dice/probabilities.h +3 -29
  13. data/games_dice.gemspec +1 -1
  14. data/lib/games_dice/bunch.rb +50 -36
  15. data/lib/games_dice/bunch_helpers.rb +35 -14
  16. data/lib/games_dice/complex_die.rb +64 -22
  17. data/lib/games_dice/complex_die_helpers.rb +122 -144
  18. data/lib/games_dice/dice.rb +61 -17
  19. data/lib/games_dice/dice_helpers.rb +37 -0
  20. data/lib/games_dice/die_result.rb +6 -8
  21. data/lib/games_dice/marshal.rb +2 -7
  22. data/lib/games_dice/parser.rb +30 -0
  23. data/lib/games_dice/probability_budget.rb +90 -0
  24. data/lib/games_dice/probability_calculation.rb +95 -0
  25. data/lib/games_dice/probability_shortcuts.rb +65 -0
  26. data/lib/games_dice/recipe_cache.rb +155 -0
  27. data/lib/games_dice/version.rb +1 -1
  28. data/lib/games_dice.rb +4 -0
  29. data/script/check_documentation.rb +10 -0
  30. data/spec/games_dice/bunch_regressions_spec.rb +46 -0
  31. data/spec/{bunch_spec.rb → games_dice/bunch_spec.rb} +65 -112
  32. data/spec/games_dice/complex_die_budget_spec.rb +60 -0
  33. data/spec/games_dice/complex_die_limits_spec.rb +119 -0
  34. data/spec/games_dice/complex_die_spec.rb +228 -0
  35. data/spec/games_dice/dice_mutation_spec.rb +165 -0
  36. data/spec/games_dice/dice_regressions_spec.rb +50 -0
  37. data/spec/games_dice/dice_spec.rb +61 -0
  38. data/spec/games_dice/die_result_spec.rb +221 -0
  39. data/spec/{die_spec.rb → games_dice/die_spec.rb} +15 -14
  40. data/spec/{map_rule_spec.rb → games_dice/map_rule_spec.rb} +8 -1
  41. data/spec/{parser_spec.rb → games_dice/parser_spec.rb} +12 -2
  42. data/spec/games_dice/probabilities_boundaries_spec.rb +161 -0
  43. data/spec/games_dice/probabilities_marshal_spec.rb +62 -0
  44. data/spec/games_dice/probabilities_spec.rb +484 -0
  45. data/spec/games_dice/probability_budget_spec.rb +17 -0
  46. data/spec/{reroll_rule_spec.rb → games_dice/reroll_rule_spec.rb} +5 -1
  47. data/spec/games_dice_spec.rb +382 -0
  48. data/spec/helpers.rb +64 -2
  49. data/spec/simple_cov_spec.rb +42 -0
  50. data/spec/support/coverage.rb +12 -0
  51. data/spec/support/validation.rb +14 -0
  52. data/spec/yard/cli/stats_spec.rb +43 -0
  53. metadata +30 -13
  54. data/spec/complex_die_spec.rb +0 -314
  55. data/spec/dice_spec.rb +0 -33
  56. data/spec/die_result_spec.rb +0 -254
  57. data/spec/fixtures/probs_fair_die_6.dat +0 -2
  58. data/spec/probability_spec.rb +0 -522
  59. data/spec/readme_spec.rb +0 -410
@@ -1,6 +1,9 @@
1
1
  // ext/games_dice/probabilities.c
2
2
 
3
3
  #include "probabilities.h"
4
+ #include <limits.h>
5
+ #include <stdint.h>
6
+ #include <math.h>
4
7
 
5
8
  // Ruby 1.8.7 compatibility patch
6
9
  #ifndef DBL2NUM
@@ -21,22 +24,68 @@ VALUE Probabilities = Qnil;
21
24
  // General utils
22
25
  //
23
26
 
24
- inline int max( int *a, int n ) {
25
- int m = -1000000000;
26
- int i;
27
- for ( i=0; i < n; i++ ) {
28
- m = a[i] > m ? a[i] : m;
27
+ /*
28
+ * Owns the temporary distributions created during one native calculation.
29
+ *
30
+ * A local C pointer does not automatically free its allocation when the
31
+ * function returns. Ruby exceptions can also leave a calculation before
32
+ * ordinary cleanup code runs, including when a later allocation fails.
33
+ *
34
+ * Run the calculation through rb_ensure with cleanup_operation as its
35
+ * cleanup callback. create_probability_list adds each new distribution
36
+ * to owned before allocating its buffers. Its fields start at zero, so
37
+ * cleanup can safely free a partially constructed distribution.
38
+ *
39
+ * owned is the head of a linked list. Each ProbabilityList stores next
40
+ * and owner_link, which points to the pointer that currently refers to
41
+ * that entry. This lets destroy_probability_list unlink an entry when
42
+ * it is freed early, preventing cleanup from freeing it again.
43
+ *
44
+ * When returning a distribution to Ruby, perform_operation wraps it in
45
+ * a Ruby object before detaching it from owned. Ruby then handles its
46
+ * lifetime. If wrapping fails, the operation still owns the distribution
47
+ * and cleanup frees it.
48
+ *
49
+ * Iteration uses the same owner for its snapshot, keeping that storage
50
+ * alive even if a Ruby callback replaces the original distribution.
51
+ * Cleanup frees the snapshot after iteration, including exceptions and
52
+ * early block exits.
53
+ */
54
+ typedef struct {
55
+ ProbabilityList *owned; /* Temporary distributions requiring cleanup. */
56
+ ProbabilityList *a, *b; /* Borrowed inputs, not owned by these pointers. */
57
+ int x, y, z; /* Arguments such as counts, weights and thresholds. */
58
+ int operation; /* Selects the calculation in perform_operation. */
59
+ } ProbabilityOperation;
60
+
61
+ static int checked_result( int64_t value ) {
62
+ if ( value < INT_MIN || value > INT_MAX ) {
63
+ rb_raise( rb_eRangeError, "Probability result is outside native integer range" );
29
64
  }
30
- return m;
65
+ return (int)value;
31
66
  }
32
67
 
33
- inline int min( int *a, int n ) {
34
- int m = 1000000000;
35
- int i;
36
- for ( i=0; i < n; i++ ) {
37
- m = a[i] < m ? a[i] : m;
68
+ static int checked_slots( int64_t slots ) {
69
+ if ( slots < 1 || slots > 1000000 ) {
70
+ rb_raise( rb_eArgError, "Bad number of probability slots" );
38
71
  }
39
- return m;
72
+ return (int)slots;
73
+ }
74
+
75
+ static int weighted_result( int a, int x, int b, int y ) {
76
+ int64_t ax = (int64_t)a * x, by = (int64_t)b * y;
77
+ if ( (by > 0 && ax > INT64_MAX - by) || (by < 0 && ax < INT64_MIN - by) ) {
78
+ rb_raise( rb_eRangeError, "Probability result is outside native integer range" );
79
+ }
80
+ return checked_result( ax + by );
81
+ }
82
+
83
+ static void detach_probability_list( ProbabilityList *pl ) {
84
+ if ( !pl->owner_link ) return;
85
+ *pl->owner_link = pl->next;
86
+ if ( pl->next ) pl->next->owner_link = pl->owner_link;
87
+ pl->next = NULL;
88
+ pl->owner_link = NULL;
40
89
  }
41
90
 
42
91
  ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -110,20 +159,19 @@ double num_arrangements( int *args, int nargs ) {
110
159
  // Probability List basics - create, delete, copy
111
160
  //
112
161
 
113
- ProbabilityList *create_probability_list() {
114
- ProbabilityList *pl;
115
- pl = malloc (sizeof(ProbabilityList));
116
- if ( pl == NULL ) {
117
- rb_raise(rb_eRuntimeError, "Could not allocate memory for Probabilities");
118
- }
119
- pl->probs = NULL;
120
- pl->cumulative = NULL;
121
- pl->slots = 0;
122
- pl->offset = 0;
162
+ ProbabilityList *create_probability_list( ProbabilityOperation *op ) {
163
+ ProbabilityList *pl = ALLOC( ProbabilityList );
164
+ memset( pl, 0, sizeof(ProbabilityList) );
165
+ pl->next = op->owned;
166
+ pl->owner_link = &op->owned;
167
+ if ( op->owned ) op->owned->owner_link = &pl->next;
168
+ op->owned = pl;
123
169
  return pl;
124
170
  }
125
171
 
126
172
  void destroy_probability_list( ProbabilityList *pl ) {
173
+ if ( !pl ) return;
174
+ detach_probability_list( pl );
127
175
  xfree( pl->cumulative );
128
176
  xfree( pl->probs );
129
177
  xfree( pl );
@@ -173,8 +221,8 @@ double *alloc_probs_iv( ProbabilityList *pl, int slots, double iv ) {
173
221
  return pr;
174
222
  }
175
223
 
176
- ProbabilityList *copy_probability_list( ProbabilityList *orig ) {
177
- ProbabilityList *pl = create_probability_list();
224
+ ProbabilityList *copy_probability_list( ProbabilityOperation *op, ProbabilityList *orig ) {
225
+ ProbabilityList *pl = create_probability_list( op );
178
226
  double *pr;
179
227
  pr = alloc_probs( pl, orig->slots );
180
228
  pl->offset = orig->offset;
@@ -183,8 +231,8 @@ ProbabilityList *copy_probability_list( ProbabilityList *orig ) {
183
231
  return pl;
184
232
  }
185
233
 
186
- inline ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
187
- ProbabilityList *pl = create_probability_list();
234
+ static ProbabilityList *new_basic_pl( ProbabilityOperation *op, int nslots, double iv, int o ) {
235
+ ProbabilityList *pl = create_probability_list( op );
188
236
  alloc_probs_iv( pl, nslots, iv );
189
237
  pl->offset = o;
190
238
  return pl;
@@ -195,21 +243,24 @@ inline ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
195
243
  // Probability List core "native" methods
196
244
  //
197
245
 
198
- inline int pl_min( ProbabilityList *pl ) {
246
+ static double pl_p_le( ProbabilityList *pl, int target );
247
+
248
+ static int pl_min( ProbabilityList *pl ) {
199
249
  return pl->offset;
200
250
  }
201
251
 
202
- inline int pl_max( ProbabilityList *pl ) {
203
- return pl->offset + pl->slots - 1;
252
+ static int pl_max( ProbabilityList *pl ) {
253
+ return checked_result( (int64_t)pl->offset + pl->slots - 1 );
204
254
  }
205
255
 
206
- ProbabilityList *pl_add_distributions( ProbabilityList *pl_a, ProbabilityList *pl_b ) {
256
+ ProbabilityList *pl_add_distributions( ProbabilityOperation *op, ProbabilityList *pl_a, ProbabilityList *pl_b ) {
207
257
  double *pr;
208
- int s = pl_a->slots + pl_b->slots - 1;
209
- int o = pl_a->offset + pl_b->offset;
258
+ int s = checked_slots( (int64_t)pl_a->slots + pl_b->slots - 1 );
259
+ int o = checked_result( (int64_t)pl_a->offset + pl_b->offset );
260
+ checked_result( (int64_t)o + s - 1 );
210
261
  int i,j;
211
262
 
212
- ProbabilityList *pl = create_probability_list();
263
+ ProbabilityList *pl = create_probability_list( op );
213
264
  pl->offset = o;
214
265
  pr = alloc_probs_iv( pl, s, 0.0 );
215
266
  for ( i=0; i < pl_a->slots; i++ ) { for ( j=0; j < pl_b->slots; j++ ) {
@@ -219,48 +270,47 @@ ProbabilityList *pl_add_distributions( ProbabilityList *pl_a, ProbabilityList *p
219
270
  return pl;
220
271
  }
221
272
 
222
- ProbabilityList *pl_add_distributions_mult( int mul_a, ProbabilityList *pl_a, int mul_b, ProbabilityList *pl_b ) {
273
+ ProbabilityList *pl_add_distributions_mult( ProbabilityOperation *op, int mul_a, ProbabilityList *pl_a, int mul_b, ProbabilityList *pl_b ) {
223
274
  int pts[4] = {
224
- mul_a * pl_min( pl_a ) + mul_b * pl_min( pl_b ),
225
- mul_a * pl_max( pl_a ) + mul_b * pl_min( pl_b ),
226
- mul_a * pl_min( pl_a ) + mul_b * pl_max( pl_b ),
227
- mul_a * pl_max( pl_a ) + mul_b * pl_max( pl_b ) };
228
-
229
- double *pr;
230
- int combined_min = min( pts, 4 );
231
- int combined_max = max( pts, 4 );
232
- int s = 1 + combined_max - combined_min;
233
- int i,j;
234
-
235
- ProbabilityList *pl = create_probability_list();
236
- pl->offset = combined_min;
237
- pr = alloc_probs_iv( pl, s, 0.0 );
238
- for ( i=0; i < pl_a->slots; i++ ) { for ( j=0; j < pl_b->slots; j++ ) {
239
- int k = mul_a * (i + pl_a->offset) + mul_b * (j + pl_b->offset) - combined_min;
240
- pr[ k ] += (pl_a->probs)[i] * (pl_b->probs)[j];
241
- } }
275
+ weighted_result( mul_a, pl_min(pl_a), mul_b, pl_min(pl_b) ),
276
+ weighted_result( mul_a, pl_max(pl_a), mul_b, pl_min(pl_b) ),
277
+ weighted_result( mul_a, pl_min(pl_a), mul_b, pl_max(pl_b) ),
278
+ weighted_result( mul_a, pl_max(pl_a), mul_b, pl_max(pl_b) ) };
279
+ int low = pts[0], high = pts[0];
280
+ for ( int i = 1; i < 4; i++ ) {
281
+ if ( pts[i] < low ) low = pts[i];
282
+ if ( pts[i] > high ) high = pts[i];
283
+ }
284
+ int slots = checked_slots( 1 + (int64_t)high - low );
285
+ ProbabilityList *pl = new_basic_pl( op, slots, 0.0, low );
286
+ for ( int i = 0; i < pl_a->slots; i++ ) {
287
+ for ( int j = 0; j < pl_b->slots; j++ ) {
288
+ int64_t value = (int64_t)mul_a * (i + pl_a->offset) + (int64_t)mul_b * (j + pl_b->offset);
289
+ pl->probs[value - low] += pl_a->probs[i] * pl_b->probs[j];
290
+ }
291
+ }
242
292
  calc_cumulative( pl );
243
293
  return pl;
244
294
  }
245
295
 
246
- inline double pl_p_eql( ProbabilityList *pl, int target ) {
247
- int idx = target - pl->offset;
296
+ static double pl_p_eql( ProbabilityList *pl, int target ) {
297
+ int64_t idx = (int64_t)target - pl->offset;
248
298
  if ( idx < 0 || idx >= pl->slots ) {
249
299
  return 0.0;
250
300
  }
251
301
  return (pl->probs)[idx];
252
302
  }
253
303
 
254
- inline double pl_p_gt( ProbabilityList *pl, int target ) {
304
+ static double pl_p_gt( ProbabilityList *pl, int target ) {
255
305
  return 1.0 - pl_p_le( pl, target );
256
306
  }
257
307
 
258
- inline double pl_p_lt( ProbabilityList *pl, int target ) {
259
- return pl_p_le( pl, target - 1 );
308
+ static double pl_p_lt( ProbabilityList *pl, int target ) {
309
+ return target == INT_MIN ? 0.0 : pl_p_le( pl, target - 1 );
260
310
  }
261
311
 
262
- inline double pl_p_le( ProbabilityList *pl, int target ) {
263
- int idx = target - pl->offset;
312
+ static double pl_p_le( ProbabilityList *pl, int target ) {
313
+ int64_t idx = (int64_t)target - pl->offset;
264
314
  if ( idx < 0 ) {
265
315
  return 0.0;
266
316
  }
@@ -270,11 +320,11 @@ inline double pl_p_le( ProbabilityList *pl, int target ) {
270
320
  return (pl->cumulative)[idx];
271
321
  }
272
322
 
273
- inline double pl_p_ge( ProbabilityList *pl, int target ) {
274
- return 1.0 - pl_p_le( pl, target - 1 );
323
+ static double pl_p_ge( ProbabilityList *pl, int target ) {
324
+ return 1.0 - pl_p_lt( pl, target );
275
325
  }
276
326
 
277
- inline double pl_expected( ProbabilityList *pl ) {
327
+ static double pl_expected( ProbabilityList *pl ) {
278
328
  double t = 0.0;
279
329
  int o = pl->offset;
280
330
  int s = pl->slots;
@@ -286,7 +336,7 @@ inline double pl_expected( ProbabilityList *pl ) {
286
336
  return t;
287
337
  }
288
338
 
289
- ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
339
+ ProbabilityList *pl_given_ge( ProbabilityOperation *op, ProbabilityList *pl, int target ) {
290
340
  int m = pl_min( pl );
291
341
  double p, mult;
292
342
  double *pr;
@@ -302,10 +352,10 @@ ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
302
352
  rb_raise( rb_eRuntimeError, "Cannot calculate given probabilities, divide by zero" );
303
353
  }
304
354
  mult = 1.0/p;
305
- s = pl->slots + pl->offset - target;
355
+ s = checked_slots( (int64_t)pl->slots + pl->offset - target );
306
356
  pr = pl->probs;
307
357
 
308
- new_pl = create_probability_list();
358
+ new_pl = create_probability_list( op );
309
359
  new_pl->offset = target;
310
360
  new_pr = alloc_probs( new_pl, s );
311
361
  o = target - pl->offset;
@@ -317,7 +367,7 @@ ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
317
367
  return new_pl;
318
368
  }
319
369
 
320
- ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
370
+ ProbabilityList *pl_given_le( ProbabilityOperation *op, ProbabilityList *pl, int target ) {
321
371
  int m = pl_max( pl );
322
372
  double p, mult;
323
373
  double *pr;
@@ -336,7 +386,7 @@ ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
336
386
  s = target - pl->offset + 1;
337
387
  pr = pl->probs;
338
388
 
339
- new_pl = create_probability_list();
389
+ new_pl = create_probability_list( op );
340
390
  new_pl->offset = pl->offset;
341
391
  new_pr = alloc_probs( new_pl, s );
342
392
 
@@ -347,34 +397,36 @@ ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
347
397
  return new_pl;
348
398
  }
349
399
 
350
- ProbabilityList *pl_repeat_sum( ProbabilityList *pl, int n ) {
400
+ ProbabilityList *pl_repeat_sum( ProbabilityOperation *op, ProbabilityList *pl, int n ) {
351
401
  ProbabilityList *pd_power = NULL;
352
402
  ProbabilityList *pd_result = NULL;
353
403
  ProbabilityList *pd_next = NULL;
354
- int power = 1;
404
+ unsigned int remaining = (unsigned int)n;
355
405
 
356
406
  if ( n < 1 ) {
357
407
  rb_raise( rb_eRuntimeError, "Cannot calculate repeat_sum when n < 1" );
358
408
  }
359
- if ( n * pl->slots - n > 1000000 ) {
409
+ if ( (int64_t)n * (pl->slots - 1) >= 1000000 ) {
360
410
  rb_raise( rb_eRuntimeError, "Too many probability slots" );
361
411
  }
362
412
 
363
- pd_power = copy_probability_list( pl );
413
+ checked_result( (int64_t)n * pl_min(pl) );
414
+ checked_result( (int64_t)n * pl_max(pl) );
415
+ pd_power = copy_probability_list( op, pl );
364
416
 
365
417
  while ( 1 ) {
366
- if ( power & n ) {
418
+ if ( remaining & 1 ) {
367
419
  if ( pd_result ) {
368
- pd_next = pl_add_distributions( pd_result, pd_power );
420
+ pd_next = pl_add_distributions( op, pd_result, pd_power );
369
421
  destroy_probability_list( pd_result );
370
422
  pd_result = pd_next;
371
423
  } else {
372
- pd_result = copy_probability_list( pd_power );
424
+ pd_result = copy_probability_list( op, pd_power );
373
425
  }
374
426
  }
375
- power = power << 1;
376
- if ( power > n ) break;
377
- pd_next = pl_add_distributions( pd_power, pd_power );
427
+ remaining >>= 1;
428
+ if ( remaining == 0 ) break;
429
+ pd_next = pl_add_distributions( op, pd_power, pd_power );
378
430
  destroy_probability_list( pd_power );
379
431
  pd_power = pd_next;
380
432
  }
@@ -398,35 +450,37 @@ void calc_p_table( ProbabilityList *pl, int q, int kbest, double *buffer ) {
398
450
  }
399
451
 
400
452
  // Assigns a list of pl variants to a buffer
401
- void calc_keep_distributions( ProbabilityList *pl, int k, int q, int kbest, ProbabilityList **pl_array ) {
453
+ void calc_keep_distributions( ProbabilityOperation *op, ProbabilityList *pl, int k, int q, int kbest, ProbabilityList **pl_array ) {
402
454
  ProbabilityList *pl_kd;
403
455
 
404
456
  int n;
405
457
  for ( n=0; n<k; n++) { pl_array[n] = NULL; }
406
- pl_array[0] = new_basic_pl( 1, 1.0, q * k );
458
+ pl_array[0] = new_basic_pl( op, 1, 1.0, checked_result( (int64_t)q * k ) );
407
459
 
408
460
  if ( kbest ) {
409
461
  if ( pl_p_gt( pl, q ) > 0.0 && k > 1 ) {
410
- pl_kd = pl_given_ge( pl, q + 1 );
462
+ pl_kd = pl_given_ge( op, pl, q + 1 );
411
463
  for ( n = 1; n < k; n++ ) {
412
- pl_array[n] = pl_repeat_sum( pl_kd, n );
413
- (pl_array[n])->offset += q * ( k - n );
464
+ pl_array[n] = pl_repeat_sum( op, pl_kd, n );
465
+ (pl_array[n])->offset = checked_result( (int64_t)(pl_array[n])->offset + (int64_t)q * (k - n) );
414
466
  }
467
+ destroy_probability_list( pl_kd );
415
468
  }
416
469
  } else {
417
470
  if ( pl_p_lt( pl, q ) > 0.0 && k > 1 ) {
418
- pl_kd = pl_given_le( pl, q - 1 );
471
+ pl_kd = pl_given_le( op, pl, q - 1 );
419
472
  for ( n = 1; n < k; n++ ) {
420
- pl_array[n] = pl_repeat_sum( pl_kd, n );
421
- (pl_array[n])->offset += q * ( k - n );
473
+ pl_array[n] = pl_repeat_sum( op, pl_kd, n );
474
+ (pl_array[n])->offset = checked_result( (int64_t)(pl_array[n])->offset + (int64_t)q * (k - n) );
422
475
  }
476
+ destroy_probability_list( pl_kd );
423
477
  }
424
478
  }
425
479
 
426
480
  return;
427
481
  }
428
482
 
429
- inline void clear_pl_array( int k, ProbabilityList **pl_array ) {
483
+ static void clear_pl_array( int k, ProbabilityList **pl_array ) {
430
484
  int n;
431
485
  for ( n=0; n<k; n++) {
432
486
  if ( pl_array[n] != NULL ) {
@@ -436,17 +490,17 @@ inline void clear_pl_array( int k, ProbabilityList **pl_array ) {
436
490
  return;
437
491
  }
438
492
 
439
- ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest ) {
493
+ ProbabilityList *pl_repeat_n_sum_k( ProbabilityOperation *op, ProbabilityList *pl, int n, int k, int kbest ) {
440
494
  // Table of probabilities ( reject, maybe, keep ) for each "pivot point"
441
495
  double p_table[3];
442
496
  int keep_combos[3];
443
497
  // Table of distributions for each count of > pivot point (vs == pivot point)
444
- ProbabilityList *keep_distributions[171];
498
+ ProbabilityList *keep_distributions[171] = { NULL };
445
499
  ProbabilityList *kd;
446
500
  ProbabilityList *pl_result = NULL;
447
501
 
448
502
  double *pr;
449
- int d = n - k;
503
+ int d;
450
504
  int i, j, q, dn, kn, mn, kdq;
451
505
  double p_sequence;
452
506
 
@@ -457,17 +511,21 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
457
511
  rb_raise( rb_eRuntimeError, "Cannot calculate repeat_sum_k when k < 1" );
458
512
  }
459
513
  if ( k >= n ) {
460
- return pl_repeat_sum( pl, n );
514
+ return pl_repeat_sum( op, pl, n );
461
515
  }
462
- if ( k * pl->slots - k >= 1000000 ) {
516
+ if ( (int64_t)k * (pl->slots - 1) >= 1000000 ) {
463
517
  rb_raise( rb_eRuntimeError, "Too many probability slots" );
464
518
  }
465
519
  if ( n > 170 ) {
466
520
  rb_raise( rb_eRuntimeError, "Too many dice to calculate combinations" );
467
521
  }
468
522
 
523
+ d = n - k;
524
+ checked_result( (int64_t)k * pl_min(pl) );
525
+ checked_result( (int64_t)k * pl_max(pl) );
526
+
469
527
  // Init target
470
- pl_result = create_probability_list();
528
+ pl_result = create_probability_list( op );
471
529
  pr = alloc_probs_iv( pl_result, 1 + k * (pl->slots - 1), 0.0 );
472
530
  pl_result->offset = pl->offset * k;
473
531
 
@@ -475,7 +533,7 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
475
533
  if ( pl->probs[i] <= 0.0 ) continue;
476
534
 
477
535
  q = i + pl->offset;
478
- calc_keep_distributions( pl, k, q, kbest, keep_distributions );
536
+ calc_keep_distributions( op, pl, k, q, kbest, keep_distributions );
479
537
  calc_p_table( pl, q, kbest, p_table );
480
538
 
481
539
  for ( kn = 0; kn < k; kn++ ) {
@@ -515,57 +573,129 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
515
573
  // Ruby integration
516
574
  //
517
575
 
518
- inline VALUE pl_as_ruby_class( ProbabilityList *pl, VALUE klass ) {
519
- return Data_Wrap_Struct( klass, 0, destroy_probability_list, pl );
576
+ static void free_probability_list( void *ptr ) {
577
+ destroy_probability_list( ptr );
578
+ }
579
+
580
+ static size_t probability_list_memsize( const void *ptr ) {
581
+ const ProbabilityList *pl = ptr;
582
+ size_t size = sizeof(ProbabilityList);
583
+
584
+ if ( pl == NULL ) return 0;
585
+ if ( pl->probs != NULL ) size += pl->slots * sizeof(double);
586
+ if ( pl->cumulative != NULL ) size += pl->slots * sizeof(double);
587
+ return size;
588
+ }
589
+
590
+ static const rb_data_type_t probability_list_type = {
591
+ .wrap_struct_name = "GamesDice::Probabilities",
592
+ .function = {
593
+ .dmark = NULL,
594
+ .dfree = free_probability_list,
595
+ .dsize = probability_list_memsize,
596
+ .dcompact = NULL,
597
+ .reserved = { NULL }
598
+ },
599
+ .parent = NULL,
600
+ .data = NULL,
601
+ .flags = 0
602
+ };
603
+
604
+ static inline VALUE pl_as_ruby_class( ProbabilityList *pl, VALUE klass ) {
605
+ return TypedData_Wrap_Struct( klass, &probability_list_type, pl );
520
606
  }
521
607
 
522
608
  VALUE pl_alloc(VALUE klass) {
523
- return pl_as_ruby_class( create_probability_list(), klass );
609
+ ProbabilityList *pl;
610
+ VALUE obj = TypedData_Make_Struct( klass, ProbabilityList, &probability_list_type, pl );
611
+ return obj;
524
612
  }
525
613
 
526
- inline ProbabilityList *get_probability_list( VALUE obj ) {
614
+ static inline ProbabilityList *get_probability_list( VALUE obj ) {
527
615
  ProbabilityList *pl;
528
- Data_Get_Struct( obj, ProbabilityList, pl );
616
+ TypedData_Get_Struct( obj, ProbabilityList, &probability_list_type, pl );
529
617
  return pl;
530
618
  }
531
619
 
532
620
  void assert_value_wraps_pl( VALUE obj ) {
533
- if ( TYPE(obj) != T_DATA ||
534
- RDATA(obj)->dfree != (RUBY_DATA_FUNC)destroy_probability_list) {
621
+ if ( ! rb_typeddata_is_kind_of( obj, &probability_list_type ) ) {
535
622
  rb_raise( rb_eTypeError, "Expected a Probabilities object, but got something else" );
536
623
  }
537
624
  }
538
625
 
539
- // Validate key/value from hash, and adjust object properties as required
540
- int validate_key_value( VALUE key, VALUE val, VALUE obj ) {
541
- int k = NUM2INT( key );
542
- ProbabilityList *pl = get_probability_list( obj );
543
- // Not assigned (to avoid "unused" warning), but this throws execption if val cannot be coerced to double
544
- NUM2DBL( val );
626
+ enum { OP_ADD, OP_WEIGHTED, OP_GE, OP_LE, OP_REPEAT, OP_KEEP, OP_COPY };
545
627
 
546
- if ( k > 0x7fffffff ) {
547
- rb_raise( rb_eArgError, "Result too large" );
548
- }
628
+ static VALUE cleanup_operation( VALUE data ) {
629
+ ProbabilityOperation *op = (ProbabilityOperation *)data;
630
+ while ( op->owned ) destroy_probability_list( op->owned );
631
+ return Qnil;
632
+ }
549
633
 
550
- if ( k < pl->offset ) {
551
- if ( pl->slots < 1 ) {
552
- pl->slots = 1;
553
- } else {
554
- pl->slots = pl->slots - k + pl->offset;
555
- }
556
- pl->offset = k;
557
- } else if ( k - pl->offset >= pl->slots ) {
558
- pl->slots = 1 + k - pl->offset;
634
+ static VALUE perform_operation( VALUE data ) {
635
+ ProbabilityOperation *op = (ProbabilityOperation *)data;
636
+ ProbabilityList *result;
637
+ switch ( op->operation ) {
638
+ case OP_ADD: result = pl_add_distributions( op, op->a, op->b ); break;
639
+ case OP_WEIGHTED: result = pl_add_distributions_mult( op, op->x, op->a, op->y, op->b ); break;
640
+ case OP_GE: result = pl_given_ge( op, op->a, op->x ); break;
641
+ case OP_LE: result = pl_given_le( op, op->a, op->x ); break;
642
+ case OP_REPEAT: result = pl_repeat_sum( op, op->a, op->x ); break;
643
+ case OP_KEEP: result = pl_repeat_n_sum_k( op, op->a, op->x, op->y, op->z ); break;
644
+ default: result = copy_probability_list( op, op->a ); break;
559
645
  }
646
+ VALUE obj = pl_as_ruby_class( result, Probabilities );
647
+ detach_probability_list( result );
648
+ return obj;
649
+ }
650
+
651
+ static VALUE operate( int operation, ProbabilityList *a, ProbabilityList *b, int x, int y, int z ) {
652
+ ProbabilityOperation op = { .owned = NULL, .a = a, .b = b, .x = x, .y = y, .z = z, .operation = operation };
653
+ return rb_ensure( perform_operation, (VALUE)&op, cleanup_operation, (VALUE)&op );
654
+ }
655
+
656
+ /* A staging Ruby object owns buffers across coercion/allocation exceptions. */
657
+ static VALUE replace_distribution( VALUE receiver, VALUE staged ) {
658
+ ProbabilityList *old = get_probability_list( receiver );
659
+ RTYPEDDATA_DATA(receiver) = get_probability_list( staged );
660
+ RTYPEDDATA_DATA(staged) = NULL;
661
+ destroy_probability_list( old );
662
+ RB_GC_GUARD(staged);
663
+ return receiver;
664
+ }
665
+
666
+ static double probability_value( VALUE value ) {
667
+ double p = NUM2DBL(value);
668
+ if ( p < 0.0 ) rb_raise( rb_eArgError, "Negative probability not allowed" );
669
+ if ( p > 1.0 ) rb_raise( rb_eArgError, "Probability must be in range 0.0..1.0" );
670
+ if ( !isfinite(p) ) rb_raise( rb_eArgError, "Probability must be finite" );
671
+ return p;
672
+ }
673
+
674
+ static void validate_mass( ProbabilityList *pl ) {
675
+ double error = calc_cumulative( pl ) - 1.0;
676
+ if ( error < -1.0e-8 ) rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
677
+ if ( error > 1.0e-8 ) rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
678
+ }
679
+
680
+ static int normalize_pair( VALUE key, VALUE value, VALUE normalized ) {
681
+ int k = NUM2INT(key);
682
+ double p = probability_value(value);
683
+ rb_hash_aset( normalized, INT2NUM(k), DBL2NUM(p) );
560
684
  return ST_CONTINUE;
561
685
  }
562
686
 
563
- // Copy key/value from hash
564
- int copy_key_value( VALUE key, VALUE val, VALUE obj ) {
565
- int k = NUM2INT( key );
566
- double v = NUM2DBL( val );
567
- ProbabilityList *pl = get_probability_list( obj );
568
- pl->probs[ k - pl->offset ] = v;
687
+ static int hash_bounds( VALUE key, VALUE value, VALUE data ) {
688
+ int *bounds = (int *)data;
689
+ int k = NUM2INT(key);
690
+ (void)value;
691
+ if ( k < bounds[0] ) bounds[0] = k;
692
+ if ( k > bounds[1] ) bounds[1] = k;
693
+ return ST_CONTINUE;
694
+ }
695
+
696
+ static int copy_pair( VALUE key, VALUE value, VALUE data ) {
697
+ ProbabilityList *pl = (ProbabilityList *)data;
698
+ pl->probs[NUM2INT(key) - pl->offset] = NUM2DBL(value);
569
699
  return ST_CONTINUE;
570
700
  }
571
701
 
@@ -582,33 +712,24 @@ int copy_key_value( VALUE key, VALUE val, VALUE obj ) {
582
712
  * @return [GamesDice::Probabilities]
583
713
  */
584
714
  VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
585
- int i, o, s;
586
- double error, p_item;
587
- ProbabilityList *pl;
588
- double *pr;
589
-
590
- o = NUM2INT(offset);
715
+ rb_check_frozen(self);
716
+ int o = NUM2INT(offset);
591
717
  Check_Type( arr, T_ARRAY );
592
- s = FIX2INT( rb_funcall( arr, rb_intern("count"), 0 ) );
593
- pl = get_probability_list( self );
594
- pl->offset = o;
595
- pr = alloc_probs( pl, s );
596
- for(i=0; i<s; i++) {
597
- p_item = NUM2DBL( rb_ary_entry( arr, i ) );
598
- if ( p_item < 0.0 ) {
599
- rb_raise( rb_eArgError, "Negative probability not allowed" );
600
- } else if ( p_item > 1.0 ) {
601
- rb_raise( rb_eArgError, "Probability must be in range 0.0..1.0" );
602
- }
603
- pr[i] = p_item;
604
- }
605
- error = calc_cumulative( pl ) - 1.0;
606
- if ( error < -1.0e-8 ) {
607
- rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
608
- } else if ( error > 1.0e-8 ) {
609
- rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
718
+ int slots = checked_slots( RARRAY_LEN(arr) );
719
+ checked_result( (int64_t)o + slots - 1 );
720
+ VALUE normalized = rb_ary_dup(arr);
721
+ for ( int i = 0; i < slots; i++ ) {
722
+ rb_ary_store( normalized, i, DBL2NUM(probability_value(rb_ary_entry(normalized, i))) );
610
723
  }
611
- return self;
724
+ VALUE staged = pl_alloc( Probabilities );
725
+ ProbabilityList *pl = get_probability_list( staged );
726
+ pl->offset = o;
727
+ alloc_probs( pl, slots );
728
+ for ( int i = 0; i < slots; i++ ) pl->probs[i] = NUM2DBL(rb_ary_entry(normalized, i));
729
+ validate_mass( pl );
730
+ RB_GC_GUARD(normalized);
731
+ rb_check_frozen(self);
732
+ return replace_distribution( self, staged );
612
733
  }
613
734
 
614
735
  /*
@@ -617,20 +738,11 @@ VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
617
738
  * @return [GamesDice::Probabilities]
618
739
  */
619
740
  VALUE probabilities_initialize_copy( VALUE copy, VALUE orig ) {
620
- ProbabilityList *pl_copy;
621
- ProbabilityList *pl_orig;
622
- double *pr;
623
-
624
- if (copy == orig) return copy;
625
- pl_copy = get_probability_list( copy );
626
- pl_orig = get_probability_list( orig );
627
-
628
- pr = alloc_probs( pl_copy, pl_orig->slots );
629
- pl_copy->offset = pl_orig->offset;
630
- memcpy( pr, pl_orig->probs, pl_orig->slots * sizeof(double) );
631
- memcpy( pl_copy->cumulative, pl_orig->cumulative, pl_orig->slots * sizeof(double) );;
632
-
633
- return copy;
741
+ if ( copy == orig ) return copy;
742
+ rb_check_frozen(copy);
743
+ VALUE staged = operate( OP_COPY, get_probability_list(orig), NULL, 0, 0, 0 );
744
+ RB_GC_GUARD(orig);
745
+ return replace_distribution( copy, staged );
634
746
  }
635
747
 
636
748
  /*
@@ -680,7 +792,8 @@ VALUE probabilities_max( VALUE self ) {
680
792
  * @return [Float] in range (0.0..1.0)
681
793
  */
682
794
  VALUE probabilites_p_eql( VALUE self, VALUE target ) {
683
- return DBL2NUM( pl_p_eql( get_probability_list( self ), NUM2INT(target) ) );
795
+ int t = NUM2INT(target);
796
+ return DBL2NUM( pl_p_eql( get_probability_list( self ), t ) );
684
797
  }
685
798
 
686
799
  /*
@@ -689,7 +802,8 @@ VALUE probabilites_p_eql( VALUE self, VALUE target ) {
689
802
  * @return [Float] in range (0.0..1.0)
690
803
  */
691
804
  VALUE probabilites_p_gt( VALUE self, VALUE target ) {
692
- return DBL2NUM( pl_p_gt( get_probability_list( self ), NUM2INT(target) ) );
805
+ int t = NUM2INT(target);
806
+ return DBL2NUM( pl_p_gt( get_probability_list( self ), t ) );
693
807
  }
694
808
 
695
809
  /*
@@ -698,7 +812,8 @@ VALUE probabilites_p_gt( VALUE self, VALUE target ) {
698
812
  * @return [Float] in range (0.0..1.0)
699
813
  */
700
814
  VALUE probabilites_p_ge( VALUE self, VALUE target ) {
701
- return DBL2NUM( pl_p_ge( get_probability_list( self ), NUM2INT(target) ) );
815
+ int t = NUM2INT(target);
816
+ return DBL2NUM( pl_p_ge( get_probability_list( self ), t ) );
702
817
  }
703
818
 
704
819
  /*
@@ -707,7 +822,8 @@ VALUE probabilites_p_ge( VALUE self, VALUE target ) {
707
822
  * @return [Float] in range (0.0..1.0)
708
823
  */
709
824
  VALUE probabilites_p_le( VALUE self, VALUE target ) {
710
- return DBL2NUM( pl_p_le( get_probability_list( self ), NUM2INT(target) ) );
825
+ int t = NUM2INT(target);
826
+ return DBL2NUM( pl_p_le( get_probability_list( self ), t ) );
711
827
  }
712
828
 
713
829
  /*
@@ -716,7 +832,8 @@ VALUE probabilites_p_le( VALUE self, VALUE target ) {
716
832
  * @return [Float] in range (0.0..1.0)
717
833
  */
718
834
  VALUE probabilites_p_lt( VALUE self, VALUE target ) {
719
- return DBL2NUM( pl_p_lt( get_probability_list( self ), NUM2INT(target) ) );
835
+ int t = NUM2INT(target);
836
+ return DBL2NUM( pl_p_lt( get_probability_list( self ), t ) );
720
837
  }
721
838
 
722
839
  /*
@@ -738,7 +855,9 @@ VALUE probabilites_expected( VALUE self ) {
738
855
  VALUE probabilities_given_ge( VALUE self, VALUE target ) {
739
856
  int t = NUM2INT(target);
740
857
  ProbabilityList *pl = get_probability_list( self );
741
- return pl_as_ruby_class( pl_given_ge( pl, t ), Probabilities );
858
+ VALUE result = operate( OP_GE, pl, NULL, t, 0, 0 );
859
+ RB_GC_GUARD(self);
860
+ return result;
742
861
  }
743
862
 
744
863
  /*
@@ -750,7 +869,9 @@ VALUE probabilities_given_ge( VALUE self, VALUE target ) {
750
869
  VALUE probabilities_given_le( VALUE self, VALUE target ) {
751
870
  int t = NUM2INT(target);
752
871
  ProbabilityList *pl = get_probability_list( self );
753
- return pl_as_ruby_class( pl_given_le( pl, t ), Probabilities );
872
+ VALUE result = operate( OP_LE, pl, NULL, t, 0, 0 );
873
+ RB_GC_GUARD(self);
874
+ return result;
754
875
  }
755
876
 
756
877
  /*
@@ -763,7 +884,9 @@ VALUE probabilities_given_le( VALUE self, VALUE target ) {
763
884
  VALUE probabilities_repeat_sum( VALUE self, VALUE nsum ) {
764
885
  int n = NUM2INT(nsum);
765
886
  ProbabilityList *pl = get_probability_list( self );
766
- return pl_as_ruby_class( pl_repeat_sum( pl, n ), Probabilities );
887
+ VALUE result = operate( OP_REPEAT, pl, NULL, n, 0, 0 );
888
+ RB_GC_GUARD(self);
889
+ return result;
767
890
  }
768
891
 
769
892
  /*
@@ -793,17 +916,15 @@ VALUE probabilities_repeat_n_sum_k( int argc, VALUE* argv, VALUE self ) {
793
916
  n = NUM2INT(nsum);
794
917
  k = NUM2INT(nkeepers);
795
918
  pl = get_probability_list( self );
796
- return pl_as_ruby_class( pl_repeat_n_sum_k( pl, n, k, keep_best ), Probabilities );
919
+ VALUE result = operate( OP_KEEP, pl, NULL, n, k, keep_best );
920
+ RB_GC_GUARD(self);
921
+ return result;
797
922
  }
798
923
 
799
- /*
800
- * Iterates through value, probability pairs
801
- * @yieldparam [Integer] result A result that may be possible in the dice scheme
802
- * @yieldparam [Float] probability Probability of result, in range 0.0..1.0
803
- * @return [GamesDice::Probabilities] this object
804
- */
805
- VALUE probabilities_each( VALUE self ) {
806
- ProbabilityList *pl = get_probability_list( self );
924
+ static VALUE yield_probability_pairs( VALUE data ) {
925
+ ProbabilityOperation *op = (ProbabilityOperation *)data;
926
+ /* Keep the snapshot native-owned until every callback has finished. */
927
+ ProbabilityList *pl = copy_probability_list( op, op->a );
807
928
  int i;
808
929
  double *pr = pl->probs;
809
930
  int o = pl->offset;
@@ -815,6 +936,19 @@ VALUE probabilities_each( VALUE self ) {
815
936
  rb_yield( a );
816
937
  }
817
938
  }
939
+ return Qnil;
940
+ }
941
+
942
+ /*
943
+ * Iterates through value, probability pairs
944
+ * @yieldparam [Integer] result A result that may be possible in the dice scheme
945
+ * @yieldparam [Float] probability Probability of result, in range 0.0..1.0
946
+ * @return [GamesDice::Probabilities] this object
947
+ */
948
+ VALUE probabilities_each( VALUE self ) {
949
+ /* Ruby callbacks may replace self's storage through marshal_load/initialize. */
950
+ ProbabilityOperation op = { .owned = NULL, .a = get_probability_list(self) };
951
+ rb_ensure( yield_probability_pairs, (VALUE)&op, cleanup_operation, (VALUE)&op );
818
952
  return self;
819
953
  }
820
954
 
@@ -849,32 +983,21 @@ VALUE probabilities_for_fair_die( VALUE self, VALUE sides ) {
849
983
  * @return [GamesDice::Probabilities]
850
984
  */
851
985
  VALUE probabilities_from_h( VALUE self, VALUE hash ) {
852
- VALUE obj;
853
- ProbabilityList *pl;
854
- double error;
855
-
856
986
  Check_Type( hash, T_HASH );
857
-
858
- obj = pl_alloc( Probabilities );
859
- pl = get_probability_list( obj );
860
-
861
-
862
- // Set these up so that they get adjusted during hash iteration
863
- pl->offset = 0x7fffffff;
864
- pl->slots = 0;
865
- // First iteration establish min/max and validate all key/values
866
- rb_hash_foreach( hash, validate_key_value, obj );
867
-
868
- alloc_probs_iv( pl, pl->slots, 0.0 );
869
- // Second iteration copy key/value pairs into structure
870
- rb_hash_foreach( hash, copy_key_value, obj );
871
-
872
- error = calc_cumulative( pl ) - 1.0;
873
- if ( error < -1.0e-8 ) {
874
- rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
875
- } else if ( error > 1.0e-8 ) {
876
- rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
877
- }
987
+ VALUE normalized = rb_hash_new();
988
+ VALUE input = rb_hash_dup(hash);
989
+ rb_hash_foreach( input, normalize_pair, normalized );
990
+ RB_GC_GUARD(input);
991
+ int bounds[2] = { INT_MAX, INT_MIN };
992
+ rb_hash_foreach( normalized, hash_bounds, (VALUE)bounds );
993
+ int slots = checked_slots( 1 + (int64_t)bounds[1] - bounds[0] );
994
+ VALUE obj = pl_alloc( Probabilities );
995
+ ProbabilityList *pl = get_probability_list( obj );
996
+ pl->offset = bounds[0];
997
+ alloc_probs_iv( pl, slots, 0.0 );
998
+ rb_hash_foreach( normalized, copy_pair, (VALUE)pl );
999
+ validate_mass( pl );
1000
+ RB_GC_GUARD(normalized);
878
1001
  return obj;
879
1002
  }
880
1003
 
@@ -887,13 +1010,17 @@ VALUE probabilities_from_h( VALUE self, VALUE hash ) {
887
1010
  * @return [GamesDice::Probabilities]
888
1011
  */
889
1012
  VALUE probabilities_add_distributions( VALUE self, VALUE gdpa, VALUE gdpb ) {
890
- ProbabilityList *pl_a = get_probability_list( gdpa );
891
- ProbabilityList *pl_b = get_probability_list( gdpb );
1013
+ ProbabilityList *pl_a;
1014
+ ProbabilityList *pl_b;
1015
+
892
1016
  assert_value_wraps_pl( gdpa );
893
1017
  assert_value_wraps_pl( gdpb );
894
1018
  pl_a = get_probability_list( gdpa );
895
1019
  pl_b = get_probability_list( gdpb );
896
- return pl_as_ruby_class( pl_add_distributions( pl_a, pl_b ), Probabilities );
1020
+ VALUE result = operate( OP_ADD, pl_a, pl_b, 0, 0, 0 );
1021
+ RB_GC_GUARD(gdpa);
1022
+ RB_GC_GUARD(gdpb);
1023
+ return result;
897
1024
  }
898
1025
 
899
1026
  /*
@@ -914,10 +1041,13 @@ VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, V
914
1041
  assert_value_wraps_pl( gdpa );
915
1042
  assert_value_wraps_pl( gdpb );
916
1043
  mul_a = NUM2INT( m_a );
917
- pl_a = get_probability_list( gdpa );
918
1044
  mul_b = NUM2INT( m_b );
1045
+ pl_a = get_probability_list( gdpa );
919
1046
  pl_b = get_probability_list( gdpb );
920
- return pl_as_ruby_class( pl_add_distributions_mult( mul_a, pl_a, mul_b, pl_b ), Probabilities );
1047
+ VALUE result = operate( OP_WEIGHTED, pl_a, pl_b, mul_a, mul_b, 0 );
1048
+ RB_GC_GUARD(gdpa);
1049
+ RB_GC_GUARD(gdpb);
1050
+ return result;
921
1051
  }
922
1052
 
923
1053
  ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -925,7 +1055,7 @@ VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, V
925
1055
  // Setup Probabilities class for Ruby interpretter
926
1056
  //
927
1057
 
928
- void init_probabilities_class() {
1058
+ void init_probabilities_class(void) {
929
1059
  VALUE GamesDice = rb_define_module("GamesDice");
930
1060
  Probabilities = rb_define_class_under( GamesDice, "Probabilities", rb_cObject );
931
1061
  rb_define_alloc_func( Probabilities, pl_alloc );