random.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. const FNV1A_32_OFFSET_BASIS = 0x811c9dc5;
  2. const FNV1A_32_PRIME = 0x01000193;
  3. /**
  4. * Computes a fast 32-bit FNV-1a hash for deterministic, non-security tasks.
  5. * Accepts any number of string/nullable-string inputs, takes measurements
  6. * to avoid collisions, and combines them into a 32-bit hash.
  7. * Not cryptographically secure; use only for non-security-related use cases.
  8. */
  9. export function hash_fnv1a32(...input: Array<string | null | undefined>): number {
  10. let hash = FNV1A_32_OFFSET_BASIS;
  11. const textEncoder = new TextEncoder();
  12. const emptyElement = textEncoder.encode('__|');
  13. for (const element of input) {
  14. if (typeof element === 'string') {
  15. hash = fnv1a32_update(hash, textEncoder.encode(element+'|'));
  16. } else if (element === null || element === undefined) {
  17. hash = fnv1a32_update(hash, emptyElement);
  18. }
  19. }
  20. return hash >>> 0;
  21. }
  22. function fnv1a32_update(hash: number, value: Uint8Array): number {
  23. for (const byte of value) {
  24. hash ^= byte;
  25. hash = Math.imul(hash, FNV1A_32_PRIME) >>> 0;
  26. }
  27. return hash;
  28. }
  29. export interface Mulberry32Sequence {
  30. next(): number;
  31. intBetween(from: number, to: number): number;
  32. floatBetween(from: number, to: number): number;
  33. }
  34. /**
  35. * Creates a fast deterministic PRNG sequence using Mulberry32.
  36. * Same seed will always produce the same sequence.
  37. * Not cryptographically secure; use only for non-security-related use cases.
  38. */
  39. export function random_mulberry32(seed: number): Mulberry32Sequence {
  40. const nextUint32 = (): number => {
  41. seed |= 0;
  42. seed = seed + 0x6D2B79F5 | 0;
  43. let imul = Math.imul(seed ^ seed >>> 15, 1 | seed);
  44. imul = imul + Math.imul(imul ^ imul >>> 7, 61 | imul) ^ imul;
  45. return (imul ^ imul >>> 14) >>> 0;
  46. };
  47. const nextNormalized = (from: number, to: number): number => {
  48. if (!Number.isFinite(from) || !Number.isFinite(to)) {
  49. throw new RangeError('from and to must be finite numbers');
  50. }
  51. if (from > to) {
  52. throw new RangeError('from must be less than or equal to to');
  53. }
  54. if (from === to) {
  55. return from;
  56. }
  57. return from + nextUint32() / 0xFFFFFFFF * (to - from);
  58. };
  59. return {
  60. next: () => {
  61. return nextUint32() / 0xFFFFFFFF;
  62. },
  63. floatBetween: (from: number, to: number): number => {
  64. return nextNormalized(from, to);
  65. },
  66. intBetween: (from: number, to: number): number => {
  67. if (!Number.isInteger(from) || !Number.isInteger(to)) {
  68. throw new RangeError('from and to must be integers');
  69. }
  70. return Math.round(nextNormalized(from, to));
  71. },
  72. };
  73. }