Class: ABMeter::Core::Utils::NumUtils
- Inherits:
-
Object
- Object
- ABMeter::Core::Utils::NumUtils
- Defined in:
- lib/abmeter/core/utils/num_utils.rb
Overview
NumUtils provides deterministic percentage assignment for A/B testing
This implementation uses SHA256 + multiply-and-shift algorithm:
-
Zero external dependencies (uses Ruby’s built-in Digest)
-
Fast execution (< 2 microseconds per assignment)
-
Cryptographically secure randomness
-
Distribution quality sufficient for A/B testing:
-
10K samples: ~3% average deviation (normal for this sample size)
-
100K samples: ~0.9% average deviation (good for statistical significance)
-
1M samples: ~0.7% average deviation (excellent uniformity)
-
The multiply-and-shift method avoids modulo bias by scaling the hash value proportionally across the entire 64-bit space before mapping to the 1-100 range.
Class Method Summary collapse
-
.percentages_to_ranges(percentages) ⇒ Object
- 10, 20, 30, 40
-
-> [(1..10) (11..30), (31..60), (61..100)].
- .to_percentage(salt, id) ⇒ Object
Class Method Details
.percentages_to_ranges(percentages) ⇒ Object
- 10, 20, 30, 40
-
-> [(1..10) (11..30), (31..60), (61..100)]
42 43 44 45 46 47 48 49 50 51 |
# File 'lib/abmeter/core/utils/num_utils.rb', line 42 def self.percentages_to_ranges(percentages) ranges = [] percentages.each do |percentage| last_end = ranges.last&.end || 0 start_val = last_end + 1 end_val = last_end + percentage ranges << (start_val..end_val) end ranges end |
.to_percentage(salt, id) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/abmeter/core/utils/num_utils.rb', line 23 def self.to_percentage(salt, id) # Use a hash function to generate a deterministic but random-looking number hash = Digest::SHA256.hexdigest("#{salt}:#{id}") # Convert first 16 characters (64 bits) to integer for better distribution # This gives us a number between 0 and 2^64-1 num = hash[0..15].to_i(16) # Industry-standard multiply-and-shift method for uniform distribution # This avoids modulo bias by scaling the 64-bit space proportionally # Formula: (num * range) >> bits = (num * 100) >> 64 # This maps [0, 2^64) uniformly to [0, 100) percentage = (num * 100) >> 64 # Add 1 to get 1-100 range instead of 0-99 percentage + 1 end |