github.com/fluhus/gostuff@v0.4.1-0.20240331134726-be71864f2b5d/morris/morris.go (about) 1 // Package morris provides an implementation of Morris's algorithm 2 // for approximate counting with few bits. 3 // 4 // The original formula raises a counter i with probability 2^(-i). 5 // The restored value is 2^i - 1. 6 // 7 // This package introduces a parameter m, so that the first m increments are 8 // made with probability 1, then m increments with probability 1/2, then m with 9 // probability 1/4... Using m=1 is equivalent to the original formula. 10 // A large m increases accuracy but costs more bits. 11 // A single counter should use the same m for all calls to Raise and Restore. 12 // 13 // This package is experimental. 14 // 15 // # Error rates 16 // 17 // Average error rates for different values of m: 18 // 19 // 1: 54.2% 20 // 3: 31.1% 21 // 10: 15.5% 22 // 30: 8.8% 23 // 100: 4.6% 24 // 300: 2.5% 25 // 1000: 1.6% 26 // 3000: 0.9% 27 // 10000: 0.5% 28 package morris 29 30 import ( 31 "fmt" 32 "math/rand" 33 34 "golang.org/x/exp/constraints" 35 ) 36 37 // If true, panics when a counter is about to be raised beyond its maximal 38 // value. 39 const checkOverFlow = true 40 41 // Raise returns the new value of i after one increment. 42 // m controls the restoration accuracy. 43 // The approximate number of calls to Raise can be restored using Restore. 44 func Raise[T constraints.Unsigned](i T, m uint) T { 45 if checkOverFlow { 46 max := T(0) - 1 47 if i == max { 48 panic(fmt.Sprintf("counter reached maximal value: %d", i)) 49 } 50 } 51 r := 1 << (uint(i) / m) 52 if rand.Intn(r) == 0 { 53 return i + 1 54 } 55 return i 56 } 57 58 // Restore returns an approximation of the number of calls to Raise on i. 59 // m should have the same value that was used with Raise. 60 func Restore[T constraints.Unsigned](i T, m uint) uint { 61 ui := uint(i) 62 if ui <= m { 63 return ui 64 } 65 return m*(1<<(ui/m)-1) + (ui%m)*(1<<(ui/m)) + (1 << (uint(i)/m - 2)) 66 }