predictable_random.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. module.exports = function nailfactory(boot) {
  2. var BOOT = boot || 0xbadebabe;
  3. function stringify(obj, replacer, spaces, cycleReplacer) {
  4. return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
  5. }
  6. function serializer(replacer, cycleReplacer) {
  7. var stack = [],
  8. keys = []
  9. if (cycleReplacer == null) cycleReplacer = function(key, value) {
  10. if (stack[0] === value) return "[Circular ~]"
  11. return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]"
  12. }
  13. /*
  14. */
  15. return function(key, value) {
  16. if (stack.length > 0) {
  17. var thisPos = stack.indexOf(this);
  18. (~thisPos) ? stack.splice(thisPos + 1) : stack.push(this);
  19. (~thisPos) ? keys.splice(thisPos, Infinity, key) : keys.push(key);
  20. if (~stack.indexOf(value)) {
  21. value = cycleReplacer.call(this, key, value)
  22. }
  23. } else {
  24. stack.push(value)
  25. }
  26. return replacer == null ? value : replacer.call(this, key, value)
  27. }
  28. }
  29. var Mash = function() {
  30. // var n = 0xefc8249d;
  31. var n = BOOT;
  32. var mash = function(data) {
  33. if (data) {
  34. data = data.toString();
  35. for (var i = 0; i < data.length; i++) {
  36. n += data.charCodeAt(i);
  37. var h = 0.02519603282416938 * n;
  38. n = h >>> 0;
  39. h -= n;
  40. h *= n;
  41. n = h >>> 0;
  42. h -= n;
  43. n += h * 0x100000000; // 2^32
  44. }
  45. return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
  46. } else {
  47. n = BOOT;
  48. }
  49. };
  50. return mash;
  51. };
  52. var uheprng = function(seed) {
  53. return (function() {
  54. var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values
  55. var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm
  56. var p = o; // init the 'phase' (max-1) of the intermediate variable pointer
  57. var s = new Array(o); // declare our intermediate variables array
  58. var i; // general purpose local
  59. var j; // general purpose local
  60. var k = 0; // general purpose local
  61. // when our "uheprng" is initially invoked our PRNG state is initialized from the
  62. // browser's own local PRNG. This is okay since although its generator might not
  63. // be wonderful, it's useful for establishing large startup entropy for our usage.
  64. var mash = new Mash(); // get a pointer to our high-performance "Mash" hash
  65. // fill the array with initial mash hash values
  66. for (i = 0; i < o; i++) {
  67. s[i] = mash(Math.random());
  68. }
  69. // this PRIVATE (internal access only) function is the heart of the multiply-with-carry
  70. // (MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a
  71. // 32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default
  72. // [0-1] return function, and by the random 'string(n)' function which returns 'n'
  73. // characters from 33 to 126.
  74. var rawprng = function() {
  75. if (++p >= o) {
  76. p = 0;
  77. }
  78. var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32
  79. return s[p] = t - (c = t | 0);
  80. };
  81. // this EXPORTED function is the default function returned by this library.
  82. // The values returned are integers in the range from 0 to range-1. We first
  83. // obtain two 32-bit fractions (from rawprng) to synthesize a single high
  84. // resolution 53-bit prng (0 to <1), then we multiply this by the caller's
  85. // "range" param and take the "floor" to return a equally probable integer.
  86. var random = function(range) {
  87. return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16)); // 2^-53
  88. };
  89. // this EXPORTED function 'string(n)' returns a pseudo-random string of
  90. // 'n' printable characters ranging from chr(33) to chr(126) inclusive.
  91. random.string = function(count) {
  92. var i;
  93. var s = '';
  94. for (i = 0; i < count; i++) {
  95. s += String.fromCharCode(33 + random(94));
  96. }
  97. return s;
  98. };
  99. // this PRIVATE "hash" function is used to evolve the generator's internal
  100. // entropy state. It is also called by the EXPORTED addEntropy() function
  101. // which is used to pour entropy into the PRNG.
  102. var hash = function() {
  103. var args = Array.prototype.slice.call(arguments);
  104. for (i = 0; i < args.length; i++) {
  105. for (j = 0; j < o; j++) {
  106. s[j] -= mash(args[i]);
  107. if (s[j] < 0) {
  108. s[j] += 1;
  109. }
  110. }
  111. }
  112. };
  113. // this EXPORTED "clean string" function removes leading and trailing spaces and non-printing
  114. // control characters, including any embedded carriage-return (CR) and line-feed (LF) characters,
  115. // from any string it is handed. this is also used by the 'hashstring' function (below) to help
  116. // users always obtain the same EFFECTIVE uheprng seeding key.
  117. random.cleanString = function(inStr) {
  118. inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ''); // remove any/all leading spaces
  119. inStr = inStr.replace(/[\x00-\x1F]/gi, ''); // remove any/all control characters
  120. inStr = inStr.replace(/\n /, '\n'); // remove any/all trailing spaces
  121. return inStr; // return the cleaned up result
  122. };
  123. // this EXPORTED "hash string" function hashes the provided character string after first removing
  124. // any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF)
  125. random.hashString = function(inStr) {
  126. inStr = random.cleanString(inStr);
  127. mash(inStr); // use the string to evolve the 'mash' state
  128. for (i = 0; i < inStr.length; i++) { // scan through the characters in our string
  129. k = inStr.charCodeAt(i); // get the character code at the location
  130. for (j = 0; j < o; j++) { // "mash" it into the UHEPRNG state
  131. s[j] -= mash(k);
  132. if (s[j] < 0) {
  133. s[j] += 1;
  134. }
  135. }
  136. }
  137. };
  138. // this EXPORTED function allows you to seed the random generator.
  139. random.seed = function(seed) {
  140. if (typeof seed === 'undefined' || seed === null) {
  141. seed = Math.random();
  142. }
  143. if (typeof seed !== 'string') {
  144. seed = stringify(seed, function(key, value) {
  145. if (typeof value === 'function') {
  146. return (value).toString();
  147. }
  148. return value;
  149. });
  150. }
  151. random.initState();
  152. random.hashString(seed);
  153. };
  154. // this handy exported function is used to add entropy to our uheprng at any time
  155. random.addEntropy = function( /* accept zero or more arguments */ ) {
  156. var args = [];
  157. for (i = 0; i < arguments.length; i++) {
  158. args.push(arguments[i]);
  159. }
  160. hash(args.join(''));
  161. };
  162. // if we want to provide a deterministic startup context for our PRNG,
  163. // but without directly setting the internal state variables, this allows
  164. // us to initialize the mash hash and PRNG's internal state before providing
  165. // some hashing input
  166. random.initState = function() {
  167. mash(); // pass a null arg to force mash hash to init
  168. for (i = 0; i < o; i++) {
  169. s[i] = mash(' '); // fill the array with initial mash hash values
  170. }
  171. c = 1; // init our multiply-with-carry carry
  172. p = o; // init our phase
  173. };
  174. // we use this (optional) exported function to signal the JavaScript interpreter
  175. // that we're finished using the "Mash" hash function so that it can free up the
  176. // local "instance variables" is will have been maintaining. It's not strictly
  177. // necessary, of course, but it's good JavaScript citizenship.
  178. random.done = function() {
  179. mash = null;
  180. };
  181. // if we called "uheprng" with a seed value, then execute random.seed() before returning
  182. if (typeof seed !== 'undefined') {
  183. random.seed(seed);
  184. }
  185. // Returns a random integer between 0 (inclusive) and range (exclusive)
  186. random.range = function(range) {
  187. return random(range);
  188. };
  189. // Returns a random float between 0 (inclusive) and 1 (exclusive)
  190. random.random = function() {
  191. return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE;
  192. };
  193. // Returns a random float between min (inclusive) and max (exclusive)
  194. random.floatBetween = function(min, max) {
  195. return random.random() * (max - min) + min;
  196. };
  197. // Returns a random integer between min (inclusive) and max (inclusive)
  198. random.intBetween = function(min, max) {
  199. return Math.floor(random.random() * (max - min + 1)) + min;
  200. };
  201. // when our main outer "uheprng" function is called, after setting up our
  202. // initial variables and entropic state, we return an "instance pointer"
  203. // to the internal anonymous function which can then be used to access
  204. // the uheprng's various exported functions. As with the ".done" function
  205. // above, we should set the returned value to 'null' once we're finished
  206. // using any of these functions.
  207. return random;
  208. }());
  209. };
  210. // Modification for use in node:
  211. uheprng.create = function(seed) {
  212. return new uheprng(seed);
  213. };
  214. return uheprng;
  215. }