tigerbeetle 0.0.36 → 0.0.38

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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/ext/tb_client/extconf.rb +13 -13
  4. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  5. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  6. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  7. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  12. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1092 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  53. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  54. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  55. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  56. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  57. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  58. data/ext/tb_client/tigerbeetle/src/direction.zig +120 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  69. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  72. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  73. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  74. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  75. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  76. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  77. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  78. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  79. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  80. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  81. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  82. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +359 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +962 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +90 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  121. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +534 -0
  122. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  124. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  125. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  126. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  127. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  130. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  131. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  139. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  140. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  141. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  146. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  147. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  164. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  165. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  166. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  196. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  197. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  205. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  206. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  208. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  209. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  210. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  211. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  212. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2928 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  242. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  243. data/lib/tb_client/shared_lib.rb +12 -5
  244. data/lib/tigerbeetle/platforms.rb +9 -0
  245. data/lib/tigerbeetle/version.rb +2 -2
  246. data/tigerbeetle.gemspec +22 -5
  247. metadata +242 -3
  248. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,677 @@
1
+ //! TigerBeetle standard Pseudo Random Number generator.
2
+ //!
3
+ //! Import qualified and use `prng` for field/variable name:
4
+ //!
5
+ //! ```
6
+ //! prng: *stdx.PRNG
7
+ //! ```
8
+ //!
9
+ //! The implementation matches Zig's `std.Random.DefaultPrng`, but we avoid using that directly in
10
+ //! order to:
11
+ //! - remove floating point from the API, to ensure determinism
12
+ //! - isolate our test suite from stdlib API churn
13
+ //! - isolate TigerBeetle from the churn in the PRNG algorithms
14
+ //! - simplify and extend the API
15
+ //! - remove dynamic-dispatch indirection (a minor bonus).
16
+
17
+ const std = @import("std");
18
+ const stdx = @import("stdx.zig");
19
+ const assert = std.debug.assert;
20
+ const math = std.math;
21
+ const Snap = stdx.Snap;
22
+ const module_path = "src/stdx";
23
+ const snap = Snap.snap_fn(module_path);
24
+ const KiB = stdx.KiB;
25
+
26
+ s: [4]u64,
27
+
28
+ const PRNG = @This();
29
+
30
+ /// A less than one rational number, used to specify probabilities.
31
+ pub const Ratio = struct {
32
+ // Invariant: numerator ≤ denominator.
33
+ numerator: u64,
34
+ // Invariant: denominator ≠ 0.
35
+ denominator: u64,
36
+
37
+ pub fn zero() Ratio {
38
+ return .{ .numerator = 0, .denominator = 1 };
39
+ }
40
+
41
+ pub fn format(
42
+ r: Ratio,
43
+ comptime fmt: []const u8,
44
+ options: std.fmt.FormatOptions,
45
+ writer: anytype,
46
+ ) !void {
47
+ _ = fmt;
48
+ _ = options;
49
+ if (r.numerator == 0) return writer.print("0", .{});
50
+ return writer.print("{d}/{d}", .{ r.numerator, r.denominator });
51
+ }
52
+
53
+ pub fn parse_flag_value(
54
+ string: []const u8,
55
+ static_diagnostic: *?[]const u8,
56
+ ) error{InvalidFlagValue}!Ratio {
57
+ assert(string.len > 0);
58
+ if (string.len == 1 and string[0] == '0') return .zero();
59
+
60
+ const string_numerator, const string_denominator = stdx.cut(string, "/") orelse {
61
+ static_diagnostic.* = "expected 'a/b' ratio, but found:";
62
+ return error.InvalidFlagValue;
63
+ };
64
+
65
+ const numerator = std.fmt.parseInt(u64, string_numerator, 10) catch {
66
+ static_diagnostic.* = "invalid numerator:";
67
+ return error.InvalidFlagValue;
68
+ };
69
+ const denominator = std.fmt.parseInt(u64, string_denominator, 10) catch {
70
+ static_diagnostic.* = "invalid denominator:";
71
+ return error.InvalidFlagValue;
72
+ };
73
+ if (denominator == 0) {
74
+ static_diagnostic.* = "denominator is zero:";
75
+ return error.InvalidFlagValue;
76
+ }
77
+ if (numerator > denominator) {
78
+ static_diagnostic.* = "ratio greater than 1:";
79
+ return error.InvalidFlagValue;
80
+ }
81
+ return ratio(numerator, denominator);
82
+ }
83
+ };
84
+
85
+ test "Ratio.parse_flag_value" {
86
+ try stdx.parse_flag_value_fuzz(Ratio, Ratio.parse_flag_value, .{
87
+ .ok = &.{
88
+ .{ "0", .zero() },
89
+ .{ "3/4", ratio(3, 4) },
90
+ .{ "10/100", ratio(10, 100) },
91
+ },
92
+ .err = &.{
93
+ .{ "1/0", "denominator is zero" },
94
+ .{ "0/0", "denominator is zero" },
95
+ .{ "3", "expected 'a/b' ratio, but found" },
96
+ .{ "π/4", "invalid numerator" },
97
+ .{ "3/i", "invalid denominator" },
98
+ .{ "4/3", "ratio greater than 1" },
99
+ },
100
+ });
101
+ }
102
+
103
+ /// Canonical constructor for Ratio. Import as `const ratio = stdx.PRNG.ratio`.
104
+ pub fn ratio(numerator: u64, denominator: u64) Ratio {
105
+ assert(denominator > 0);
106
+ assert(numerator <= denominator);
107
+ return .{ .numerator = numerator, .denominator = denominator };
108
+ }
109
+
110
+ pub fn from_seed(seed: u64) PRNG {
111
+ var s = seed;
112
+ return .{ .s = .{
113
+ split_mix_64(&s),
114
+ split_mix_64(&s),
115
+ split_mix_64(&s),
116
+ split_mix_64(&s),
117
+ } };
118
+ }
119
+
120
+ pub fn from_seed_testing() PRNG {
121
+ comptime assert(@import("builtin").is_test);
122
+ return .from_seed(std.testing.random_seed);
123
+ }
124
+
125
+ fn split_mix_64(s: *u64) u64 {
126
+ s.* +%= 0x9e3779b97f4a7c15;
127
+
128
+ var z = s.*;
129
+ z = (z ^ (z >> 30)) *% 0xbf58476d1ce4e5b9;
130
+ z = (z ^ (z >> 27)) *% 0x94d049bb133111eb;
131
+ return z ^ (z >> 31);
132
+ }
133
+
134
+ fn next(prng: *PRNG) u64 {
135
+ const r = std.math.rotl(u64, prng.s[0] +% prng.s[3], 23) +% prng.s[0];
136
+
137
+ const t = prng.s[1] << 17;
138
+
139
+ prng.s[2] ^= prng.s[0];
140
+ prng.s[3] ^= prng.s[1];
141
+ prng.s[1] ^= prng.s[2];
142
+ prng.s[0] ^= prng.s[3];
143
+
144
+ prng.s[2] ^= t;
145
+
146
+ prng.s[3] = math.rotl(u64, prng.s[3], 45);
147
+
148
+ return r;
149
+ }
150
+
151
+ test next {
152
+ var prng = from_seed(92);
153
+ var distribution: [8]u32 = @splat(0);
154
+ for (0..1000) |_| {
155
+ distribution[prng.next() % 8] += 1;
156
+ }
157
+ try snap(@src(),
158
+ \\{ 134, 134, 117, 121, 117, 128, 131, 118 }
159
+ ).diff_fmt("{d}", .{distribution});
160
+ }
161
+
162
+ pub fn fill(prng: *PRNG, target: []u8) void {
163
+ var i: usize = 0;
164
+ const aligned_len = target.len - (target.len & 7);
165
+
166
+ // Complete 8 byte segments.
167
+ while (i < aligned_len) : (i += 8) {
168
+ var n = prng.next();
169
+ comptime var j: usize = 0;
170
+ inline while (j < 8) : (j += 1) {
171
+ target[i + j] = @as(u8, @truncate(n));
172
+ n >>= 8;
173
+ }
174
+ }
175
+
176
+ // Remaining (cuts the stream).
177
+ if (i != target.len) {
178
+ var n = prng.next();
179
+ while (i < target.len) : (i += 1) {
180
+ target[i] = @as(u8, @truncate(n));
181
+ n >>= 8;
182
+ }
183
+ }
184
+ }
185
+
186
+ test fill {
187
+ const size_max = 128;
188
+ var buffer_max: [size_max]u8 = undefined;
189
+ var prng = from_seed(32);
190
+
191
+ var distribution: [8]u32 = @splat(0);
192
+ for (0..size_max + 1) |size| {
193
+ // Check that the entire buffer is filled, by filling it over a couple of times
194
+ // and checking that each byte is non-zero at least once.
195
+ var non_zero: stdx.BitSetType(size_max) = .{};
196
+ for (0..3) |_| {
197
+ const buffer = buffer_max[0..size];
198
+ @memset(buffer, 0);
199
+ prng.fill(buffer);
200
+ for (buffer, 0..) |byte, i| {
201
+ distribution[byte % 8] += 1;
202
+ if (byte != 0) non_zero.set(i);
203
+ }
204
+ }
205
+ for (0..size) |i| assert(non_zero.is_set(i));
206
+ }
207
+
208
+ try snap(@src(),
209
+ \\{ 3120, 3084, 3089, 3103, 3092, 3120, 3074, 3086 }
210
+ ).diff_fmt("{d}", .{distribution});
211
+ }
212
+
213
+ /// Generate an unbiased, uniformly distributed integer r such that 0 ≤ r ≤ max.
214
+ ///
215
+ /// No biased version is provided --- while biased generation is simpler&faster, the bias can be
216
+ /// quite high depending on max!
217
+ pub fn int_inclusive(prng: *PRNG, Int: anytype, max: Int) Int {
218
+ comptime assert(@typeInfo(Int).int.signedness == .unsigned);
219
+ if (max == std.math.maxInt(Int)) {
220
+ return prng.int(Int);
221
+ }
222
+
223
+ comptime assert(@typeInfo(Int).int.signedness == .unsigned);
224
+ const bits = @typeInfo(Int).int.bits;
225
+ const less_than = max + 1;
226
+
227
+ // adapted from:
228
+ // http://www.pcg-random.org/posts/bounded-rands.html
229
+ // "Lemire's (with an extra tweak from Zig)"
230
+ var x = prng.int(Int);
231
+ var m = math.mulWide(Int, x, less_than);
232
+ var l: Int = @truncate(m);
233
+ if (l < less_than) {
234
+ var t = -%less_than;
235
+
236
+ if (t >= less_than) {
237
+ t -= less_than;
238
+ if (t >= less_than) {
239
+ t %= less_than;
240
+ }
241
+ }
242
+ while (l < t) {
243
+ x = prng.int(Int);
244
+ m = math.mulWide(Int, x, less_than);
245
+ l = @truncate(m);
246
+ }
247
+ }
248
+ return @intCast(m >> bits);
249
+ }
250
+
251
+ test int_inclusive {
252
+ var prng = from_seed(92);
253
+ for (0..8) |max_usize| {
254
+ const max: u8 = @intCast(max_usize);
255
+ var distribution: [8]u32 = @splat(0);
256
+ for (0..100) |_| {
257
+ distribution[prng.int_inclusive(u8, max)] += 1;
258
+ }
259
+ for (distribution[0 .. max + 1]) |d| assert(d > 0);
260
+ for (distribution[max + 1 ..]) |d| assert(d == 0);
261
+ }
262
+
263
+ var distribution: [8]u32 = @splat(0);
264
+ for (0..1000) |_| {
265
+ const n = prng.int_inclusive(u128, 7);
266
+ distribution[@intCast(n)] += 1;
267
+ }
268
+ try snap(@src(),
269
+ \\{ 123, 127, 115, 125, 125, 139, 111, 135 }
270
+ ).diff_fmt("{d}", .{distribution});
271
+
272
+ var large: u32 = 0;
273
+ var small: u32 = 0;
274
+ for (0..1000) |_| {
275
+ if (prng.int_inclusive(u64, math.maxInt(u64) / 2) > math.maxInt(u64) / 4) {
276
+ large += 1;
277
+ } else {
278
+ small += 1;
279
+ }
280
+ }
281
+ try snap(@src(),
282
+ \\large=506 small=494
283
+ ).diff_fmt("large={} small={}", .{ large, small });
284
+ }
285
+
286
+ // Deliberately excluded from the API to normalize everything to closed ranges.
287
+ // Somewhat surprisingly, closed ranges are more convenient for generating random numbers:
288
+ // - passing zero is not a subtle error
289
+ // - passing intMax allows generating any integer
290
+ // - at the call-site, inclusive is usually somewhat more obvious.
291
+ pub const int_exclusive = @compileError("intentionally not implemented");
292
+
293
+ /// Given a slice, generates a random valid index for the slice.
294
+ pub fn index(prng: *PRNG, slice: anytype) usize {
295
+ assert(slice.len > 0);
296
+ return prng.int_inclusive(usize, slice.len - 1);
297
+ }
298
+
299
+ test index {
300
+ var prng = from_seed(92);
301
+
302
+ var distribution: [8]u32 = @splat(0);
303
+ for (0..100) |_| {
304
+ distribution[index(&prng, &distribution)] += 1;
305
+ }
306
+ try snap(@src(),
307
+ \\{ 9, 13, 13, 11, 10, 16, 16, 12 }
308
+ ).diff_fmt("{d}", .{distribution});
309
+ }
310
+
311
+ /// Generates a uniform, unbiased integer r such that max ≤ r ≤ max.
312
+ pub fn range_inclusive(prng: *PRNG, Int: type, min: Int, max: Int) Int {
313
+ comptime assert(@typeInfo(Int).int.signedness == .unsigned);
314
+ assert(min <= max);
315
+ return min + prng.int_inclusive(Int, max - min);
316
+ }
317
+
318
+ test range_inclusive {
319
+ var prng = from_seed(92);
320
+ for (0..8) |min| {
321
+ for (min..8) |max| {
322
+ var distribution: [8]u32 = @splat(0);
323
+ for (0..100) |_| {
324
+ distribution[prng.range_inclusive(usize, min, max)] += 1;
325
+ }
326
+ for (distribution, 0..) |d, i| {
327
+ assert((d > 0) == (min <= i and i <= max));
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ /// Returns a uniformly distributed integer of type T.
334
+ ///
335
+ /// That is, fills @sizeOf(T) bytes with random bits.
336
+ pub fn int(prng: *PRNG, Int: type) Int {
337
+ comptime assert(@typeInfo(Int).int.signedness == .unsigned);
338
+ if (Int == u64) return prng.next();
339
+ if (@sizeOf(Int) < @sizeOf(u64)) return @truncate(prng.next());
340
+ var result: Int = undefined;
341
+ prng.fill(std.mem.asBytes(&result));
342
+ return result;
343
+ }
344
+
345
+ test int {
346
+ try test_bytes_int(u8, snap(@src(),
347
+ \\{ 134, 134, 117, 121, 117, 128, 131, 118 }
348
+ ));
349
+ try test_bytes_int(u64, snap(@src(),
350
+ \\{ 134, 134, 117, 121, 117, 128, 131, 118 }
351
+ ));
352
+ try test_bytes_int(u128, snap(@src(),
353
+ \\{ 130, 143, 107, 135, 111, 119, 132, 123 }
354
+ ));
355
+ }
356
+
357
+ fn test_bytes_int(Int: type, want: Snap) !void {
358
+ var prng = PRNG.from_seed(92);
359
+ var distribution: [8]u32 = @splat(0);
360
+ for (0..1000) |_| {
361
+ distribution[@intCast(prng.int(Int) % 8)] += 1;
362
+ }
363
+ try want.diff_fmt("{d}", .{distribution});
364
+ }
365
+
366
+ /// Returns true with probability 0.5.
367
+ pub fn boolean(prng: *PRNG) bool {
368
+ return prng.next() & 1 == 1;
369
+ }
370
+
371
+ test boolean {
372
+ var prng = PRNG.from_seed(92);
373
+ var heads: u32 = 0;
374
+ var tails: u32 = 0;
375
+ for (0..1000) |_| {
376
+ if (prng.boolean()) heads += 1 else tails += 1;
377
+ }
378
+ try snap(@src(),
379
+ \\heads = 501 tails = 499
380
+ ).diff_fmt("heads = {} tails = {}", .{ heads, tails });
381
+ }
382
+
383
+ /// Returns a Word with a single randomly-chosen bit set.
384
+ pub fn bit(prng: *PRNG, comptime Word: type) Word {
385
+ comptime assert(@typeInfo(Word) == .int);
386
+ comptime assert(@typeInfo(Word).int.signedness == .unsigned);
387
+ return @as(Word, 1) << prng.int_inclusive(std.math.Log2Int(Word), @bitSizeOf(Word) - 1);
388
+ }
389
+
390
+ test bit {
391
+ var prng = PRNG.from_seed(92);
392
+ var hits: [8]u32 = @splat(0);
393
+ for (0..1000) |_| {
394
+ const word = prng.bit(u8);
395
+ assert(@popCount(word) == 1);
396
+ hits[@ctz(word)] += 1;
397
+ }
398
+ try snap(@src(),
399
+ \\{ 134, 134, 117, 121, 117, 128, 131, 118 }
400
+ ).diff_fmt("{any}", .{hits});
401
+ }
402
+
403
+ /// Returns true with the given rational probability.
404
+ pub fn chance(prng: *PRNG, probability: Ratio) bool {
405
+ assert(probability.denominator > 0);
406
+ assert(probability.numerator <= probability.denominator);
407
+ return prng.int_inclusive(u64, probability.denominator - 1) < probability.numerator;
408
+ }
409
+
410
+ test chance {
411
+ var prng = PRNG.from_seed(92);
412
+ var balance: i32 = 0;
413
+ for (0..1000) |_| {
414
+ if (prng.chance(ratio(2, 7))) balance += 1 else balance -= 1;
415
+ if (prng.chance(ratio(5, 7))) balance += 1 else balance -= 1;
416
+ }
417
+ try snap(@src(),
418
+ \\balance = 46
419
+ ).diff_fmt("balance = {d}", .{balance});
420
+ }
421
+
422
+ /// Like enum_weighted, but doesn't require specifying the enum up-front.
423
+ pub fn chances(prng: *PRNG, weights: anytype) std.meta.FieldEnum(@TypeOf(weights)) {
424
+ const Enum = std.meta.FieldEnum(@TypeOf(weights));
425
+ return enum_weighted_impl(prng, Enum, weights);
426
+ }
427
+
428
+ test chances {
429
+ var prng = from_seed(92);
430
+ var count: struct { a: u32 = 0, b: u32 = 0, c: u32 = 0 } = .{};
431
+ for (0..1000) |_| {
432
+ switch (prng.chances(.{ .a = 1, .b = 3, .c = 2 })) {
433
+ inline else => |tag| @field(count, @tagName(tag)) += 1,
434
+ }
435
+ }
436
+ try snap(@src(),
437
+ \\a=166 b=475 c=359
438
+ ).diff_fmt("a={} b={} c={}", .{ count.a, count.b, count.c });
439
+ }
440
+
441
+ pub fn error_uniform(prng: *PRNG, Error: type) Error {
442
+ const errors = @typeInfo(Error).error_set.?;
443
+ return switch (prng.index(errors)) {
444
+ inline 0...(errors.len - 1) => |i| @field(Error, errors[i].name),
445
+ else => unreachable,
446
+ };
447
+ }
448
+
449
+ /// Returns a random value of an enum.
450
+ pub fn enum_uniform(prng: *PRNG, Enum: type) Enum {
451
+ const values = std.enums.values(Enum);
452
+ return values[prng.index(values)];
453
+ }
454
+
455
+ test enum_uniform {
456
+ const E = enum(u8) { a, b, c = 8 }; // 8 tests that the discriminant is used properly.
457
+
458
+ var prng = from_seed(92);
459
+ var count: struct { a: u32 = 0, b: u32 = 0, c: u32 = 0 } = .{};
460
+ for (0..1000) |_| {
461
+ switch (prng.enum_uniform(E)) {
462
+ inline else => |tag| @field(count, @tagName(tag)) += 1,
463
+ }
464
+ }
465
+
466
+ try snap(@src(),
467
+ \\a=318 b=323 c=359
468
+ ).diff_fmt("a={} b={} c={}", .{ count.a, count.b, count.c });
469
+ }
470
+
471
+ pub fn EnumWeightsType(E: type) type {
472
+ return std.enums.EnumFieldStruct(E, u64, null);
473
+ }
474
+
475
+ /// Returns a random value of an enum, where probability is proportional to weight.
476
+ pub fn enum_weighted(prng: *PRNG, Enum: type, weights: EnumWeightsType(Enum)) Enum {
477
+ return enum_weighted_impl(prng, Enum, weights);
478
+ }
479
+
480
+ fn enum_weighted_impl(prng: *PRNG, Enum: type, weights: anytype) Enum {
481
+ const fields = @typeInfo(Enum).@"enum".fields;
482
+ var total: u64 = 0;
483
+ inline for (fields) |field| {
484
+ total += @field(weights, field.name);
485
+ }
486
+ assert(total > 0);
487
+ var pick = prng.int_inclusive(u64, total - 1);
488
+ inline for (fields) |field| {
489
+ const weight = @field(weights, field.name);
490
+ if (pick < weight) return @as(Enum, @enumFromInt(field.value));
491
+ pick -= weight;
492
+ }
493
+ unreachable;
494
+ }
495
+
496
+ test enum_weighted {
497
+ const E = enum(u8) { a, b, c = 8 }; // 8 tests that the discriminant is used properly.
498
+
499
+ var prng = from_seed(92);
500
+ var count: struct { a: u32 = 0, b: u32 = 0, c: u32 = 0 } = .{};
501
+ for (0..1000) |_| {
502
+ switch (prng.enum_weighted(E, .{ .a = 0, .b = 1, .c = 2 })) {
503
+ inline else => |tag| @field(count, @tagName(tag)) += 1,
504
+ }
505
+ }
506
+
507
+ try snap(@src(),
508
+ \\a=0 b=318 c=682
509
+ ).diff_fmt("a={} b={} c={}", .{ count.a, count.b, count.c });
510
+ }
511
+
512
+ /// Return a distribution for use with `random_enum`.
513
+ ///
514
+ /// This is swarm testing: some variants are disabled completely,
515
+ /// and the rest have wildly different probabilities.
516
+ pub fn enum_weights(
517
+ prng: *PRNG,
518
+ comptime Enum: type,
519
+ ) EnumWeightsType(Enum) {
520
+ const fields = comptime std.meta.fieldNames(Enum);
521
+
522
+ var combination = PRNG.Combination.init(.{
523
+ .total = fields.len,
524
+ .sample = prng.range_inclusive(u32, 1, fields.len),
525
+ });
526
+ defer assert(combination.done());
527
+
528
+ var weights: PRNG.EnumWeightsType(Enum) = undefined;
529
+ inline for (fields) |field| {
530
+ @field(weights, field) = if (combination.take(prng))
531
+ prng.range_inclusive(u64, 1, 100)
532
+ else
533
+ 0;
534
+ }
535
+
536
+ return weights;
537
+ }
538
+
539
+ /// An iterator-style API for selecting a random combination of elements.
540
+ pub const Combination = struct {
541
+ total: u32,
542
+ sample: u32,
543
+
544
+ taken: u32,
545
+ seen: u32,
546
+
547
+ pub fn init(options: struct { total: u32, sample: u32 }) Combination {
548
+ assert(options.sample <= options.total);
549
+ return .{
550
+ .total = options.total,
551
+ .sample = options.sample,
552
+ .taken = 0,
553
+ .seen = 0,
554
+ };
555
+ }
556
+
557
+ pub fn done(combination: *const Combination) bool {
558
+ return combination.taken == combination.sample and
559
+ combination.seen == combination.total;
560
+ }
561
+
562
+ pub fn take(combination: *Combination, prng: *PRNG) bool {
563
+ assert(combination.seen < combination.total);
564
+ assert(combination.taken <= combination.sample);
565
+
566
+ const n = combination.total - combination.seen;
567
+ const k = combination.sample - combination.taken;
568
+ const result = prng.chance(ratio(k, n));
569
+
570
+ combination.seen += 1;
571
+ if (result) combination.taken += 1;
572
+ return result;
573
+ }
574
+ };
575
+
576
+ test Combination {
577
+ var prng = from_seed(92);
578
+
579
+ const pool: [7]u8 = "abcdefg".*;
580
+ var result: [3]u8 = undefined;
581
+ var result_count: usize = 0;
582
+
583
+ var e_taken_count: u32 = 0;
584
+ for (0..1000) |_| {
585
+ result_count = 0;
586
+ var combination = Combination.init(.{ .total = pool.len, .sample = 3 });
587
+ for (pool) |x| {
588
+ if (combination.take(&prng)) {
589
+ result[result_count] = x;
590
+ result_count += 1;
591
+ }
592
+ }
593
+ assert(combination.done());
594
+ assert(result_count == 3);
595
+
596
+ e_taken_count += @intFromBool(std.mem.indexOfScalar(u8, &result, 'e') != null);
597
+ }
598
+
599
+ try snap(@src(),
600
+ \\e_taken_count = 432 expected_value=428
601
+ ).diff_fmt("e_taken_count = {} expected_value={}", .{ e_taken_count, 1000 * 3 / 7 });
602
+ }
603
+
604
+ /// An iterator style API for selecting a single element out of the given weighted sequence,
605
+ /// without a priori knowledge about the total weight.
606
+ pub const Reservoir = struct {
607
+ total: u64,
608
+
609
+ pub fn init() Reservoir {
610
+ return .{ .total = 0 };
611
+ }
612
+
613
+ pub fn replace(reservoir: *Reservoir, prng: *PRNG, weight: u64) bool {
614
+ reservoir.total += weight;
615
+ return prng.chance(ratio(weight, reservoir.total));
616
+ }
617
+ };
618
+
619
+ test Reservoir {
620
+ var prng = from_seed(92);
621
+ const animals: []const []const u8 = &.{ "walrus", "kiwi", "capybara", "platypus" };
622
+ var kiwi_count: u32 = 0;
623
+
624
+ for (0..1000) |_| {
625
+ var reservoir = Reservoir.init();
626
+ var pick: ?[]const u8 = null;
627
+ for (animals) |animal| {
628
+ if (reservoir.replace(&prng, animal.len)) pick = animal;
629
+ }
630
+ assert(pick != null);
631
+ kiwi_count += @intFromBool(std.mem.eql(u8, pick.?, "kiwi"));
632
+ }
633
+
634
+ var total_weight: u64 = 0;
635
+ for (animals) |animal| total_weight += animal.len;
636
+ const expected_value = 1000 * "kiwi".len / total_weight;
637
+
638
+ try snap(@src(),
639
+ \\kiwi_count = 141 expected_value=153
640
+ ).diff_fmt("kiwi_count = {} expected_value={}", .{ kiwi_count, expected_value });
641
+ }
642
+
643
+ pub fn shuffle(prng: *PRNG, T: type, slice: []T) void {
644
+ for (0..slice.len) |i| {
645
+ const j = prng.int_inclusive(u64, i);
646
+ std.mem.swap(T, &slice[i], &slice[j]);
647
+ }
648
+ }
649
+
650
+ test shuffle {
651
+ var prng = from_seed(92);
652
+ var g_first_count: u32 = 0;
653
+
654
+ for (0..1000) |_| {
655
+ var buffer = "abcdefg".*;
656
+ shuffle(&prng, u8, &buffer);
657
+ g_first_count += @intFromBool(buffer[0] == 'g');
658
+ }
659
+
660
+ try snap(@src(),
661
+ \\g_first_count = 152 expected_value=142
662
+ ).diff_fmt("g_first_count = {} expected_value={}", .{ g_first_count, 1000 / 7 });
663
+ }
664
+
665
+ test "no floating point please" {
666
+ const path = try std.fs.path.join(std.testing.allocator, &.{
667
+ module_path,
668
+ @src().file,
669
+ });
670
+ defer std.testing.allocator.free(path);
671
+
672
+ const file_text = try std.fs.cwd().readFileAlloc(std.testing.allocator, path, 64 * KiB);
673
+ defer std.testing.allocator.free(file_text);
674
+
675
+ assert(std.mem.indexOf(u8, file_text, "f" ++ "32") == null);
676
+ assert(std.mem.indexOf(u8, file_text, "f" ++ "64") == null);
677
+ }