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  }