Module: PseudoRandom::Seed

Defined in:
lib/pseudo_random/seed.rb

Overview

Internal seed canonicalization & hashing (FNV-1a 64-bit) Uses C++ implementation if available, otherwise pure Ruby

Constant Summary collapse

FNV_OFFSET =
0xcbf29ce484222325
FNV_PRIME =
0x100000001b3
MASK64 =
0xffff_ffff_ffff_ffff

Class Method Summary collapse

Class Method Details

.canonical_each_byte(obj) ⇒ Object

Depth-first canonical serialization streamed as bytes



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/pseudo_random/seed.rb', line 31

def canonical_each_byte(obj, ...)
  case obj
  when NilClass
    yield 'n'.ord
  when TrueClass
    yield 't'.ord
  when FalseClass
    yield 'f'.ord
  when Integer
    yield 'i'.ord
    encode_varint(zigzag(obj), ...)
  when Float
    yield 'd'.ord
    [obj].pack('G').each_byte(...)
  when String
    str = obj.encode(Encoding::UTF_8)
    yield 's'.ord
    encode_varint(str.bytesize, ...)
    str.each_byte(...)
  when Symbol
    str = obj.to_s.encode(Encoding::UTF_8)
    yield 'y'.ord
    encode_varint(str.bytesize, ...)
    str.each_byte(...)
  when Array
    yield 'a'.ord
    encode_varint(obj.length, ...)
    obj.each { |e| canonical_each_byte(e, ...) }
  when Hash
    yield 'h'.ord
    encode_varint(obj.length, ...)
    # Canonical order by key string representation to avoid insertion order dependence
    obj.keys.map(&:to_s).sort.each do |ks|
      canonical_each_byte(ks, ...)
      original_key = if obj.key?(ks)
                       ks
                     elsif obj.key?(ks.to_sym)
                       ks.to_sym
                     else
                       # Fallback (should not usually happen)
                       obj.keys.find { |k| k.to_s == ks }
                     end
      canonical_each_byte(obj[original_key], ...)
    end
  when Time
    yield 'T'.ord
    encode_varint(obj.to_i, ...)
    encode_varint(obj.nsec, ...)
  else
    # Fallback: class name + ':' + to_s (could cause collisions if to_s not stable)
    rep = "#{obj.class.name}:#{obj}"
    rep = rep.encode(Encoding::UTF_8)
    yield 'o'.ord
    encode_varint(rep.bytesize, ...)
    rep.each_byte(...)
  end
end

.encode_varint(num) ⇒ Object

Varint (7-bit continuation) encoding

Raises:

  • (ArgumentError)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/pseudo_random/seed.rb', line 95

def encode_varint(num)
  raise ArgumentError, 'negative varint' if num < 0

  loop do
    byte = num & 0x7f
    num >>= 7
    if num.zero?
      yield byte
      break
    else
      yield(byte | 0x80)
    end
  end
end

.to_seed_int(obj) ⇒ Object

Public: Convert arbitrary Ruby object to a deterministic 31-bit Integer for Random.new



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/pseudo_random/seed.rb', line 14

def to_seed_int(obj)
  if PseudoRandom.native_extension_loaded?
    # Use C++ implementation for better performance
    PseudoRandom::SeedNative.to_seed_int(obj)
  else
    # Fall back to Ruby implementation
    h = FNV_OFFSET
    canonical_each_byte(obj) do |byte|
      h ^= byte
      h = (h * FNV_PRIME) & MASK64
    end
    s = h ^ (h >> 32)
    s & 0x7fff_ffff
  end
end

.zigzag(num) ⇒ Object

ZigZag encode signed -> unsigned integer



90
91
92
# File 'lib/pseudo_random/seed.rb', line 90

def zigzag(num)
  num >= 0 ? (num << 1) : ((-num << 1) - 1)
end