github.com/network-quality/goresponsiveness@v0.0.0-20240129151524-343954285090/utilities/math.go (about)

     1  /*
     2   * This file is part of Go Responsiveness.
     3   *
     4   * Go Responsiveness is free software: you can redistribute it and/or modify it under
     5   * the terms of the GNU General Public License as published by the Free Software Foundation,
     6   * either version 2 of the License, or (at your option) any later version.
     7   * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
     8   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
     9   * PARTICULAR PURPOSE. See the GNU General Public License for more details.
    10   *
    11   * You should have received a copy of the GNU General Public License along
    12   * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
    13   */
    14  
    15  package utilities
    16  
    17  import (
    18  	"math"
    19  	"sort"
    20  
    21  	"golang.org/x/exp/constraints"
    22  	"golang.org/x/exp/slices"
    23  )
    24  
    25  type Number interface {
    26  	constraints.Float | constraints.Integer
    27  }
    28  
    29  func CalculateAverage[T Number](elements []T) float64 {
    30  	total := T(0)
    31  	for i := 0; i < len(elements); i++ {
    32  		total += elements[i]
    33  	}
    34  	return float64(total) / float64(len(elements))
    35  }
    36  
    37  func CalculatePercentile[T Number](
    38  	elements []T,
    39  	p uint,
    40  ) (result T) {
    41  	result = T(0)
    42  	if p < 1 || p > 100 {
    43  		return
    44  	}
    45  
    46  	sort.Slice(elements, func(l int, r int) bool { return elements[l] < elements[r] })
    47  	pindex := int((float64(p) / float64(100)) * float64(len(elements)))
    48  	if pindex >= len(elements) {
    49  		return
    50  	}
    51  	result = elements[pindex]
    52  	return
    53  }
    54  
    55  func Max(x, y uint64) uint64 {
    56  	if x > y {
    57  		return x
    58  	}
    59  	return y
    60  }
    61  
    62  func SignedPercentDifference[T constraints.Float | constraints.Integer](
    63  	current T,
    64  	previous T,
    65  ) (difference float64) {
    66  	fCurrent := float64(current)
    67  	fPrevious := float64(previous)
    68  	return ((fCurrent - fPrevious) / fPrevious) * 100.0
    69  }
    70  
    71  func AbsPercentDifference(
    72  	current float64,
    73  	previous float64,
    74  ) (difference float64) {
    75  	return (math.Abs(current-previous) / (float64(current+previous) / 2.0)) * float64(
    76  		100,
    77  	)
    78  }
    79  
    80  func CalculateStandardDeviation[T constraints.Float | constraints.Integer](elements []T) float64 {
    81  	// From https://www.mathsisfun.com/data/standard-deviation-calculator.html
    82  	// Yes, for real!
    83  
    84  	// Calculate the average of the numbers ...
    85  	average := CalculateAverage(elements)
    86  
    87  	// Calculate the square of each of the elements' differences from the mean.
    88  	differences_squared := make([]float64, len(elements))
    89  	for index, value := range elements {
    90  		differences_squared[index] = math.Pow(float64(value-T(average)), 2)
    91  	}
    92  
    93  	// The variance is the average of the squared differences.
    94  	// So, we need to ...
    95  
    96  	// Accumulate all those squared differences.
    97  	sds := float64(0)
    98  	for _, dss := range differences_squared {
    99  		sds += dss
   100  	}
   101  
   102  	// And then divide that total by the number of elements
   103  	variance := sds / float64(len(elements))
   104  
   105  	// Finally, the standard deviation is the square root
   106  	// of the variance.
   107  	sd := float64(math.Sqrt(variance))
   108  	// sd := T(variance)
   109  
   110  	return sd
   111  }
   112  
   113  func AllSequentialIncreasesLessThan[T Number](elements []T, limit float64) (bool, float64) {
   114  	if len(elements) < 2 {
   115  		return false, 0.0
   116  	}
   117  
   118  	maximumSequentialIncrease := float64(0)
   119  	for i := 1; i < len(elements); i++ {
   120  		current := elements[i]
   121  		previous := elements[i-1]
   122  		percentChange := SignedPercentDifference(current, previous)
   123  		if percentChange > limit {
   124  			return false, percentChange
   125  		}
   126  		if percentChange > float64(maximumSequentialIncrease) {
   127  			maximumSequentialIncrease = percentChange
   128  		}
   129  	}
   130  	return true, maximumSequentialIncrease
   131  }
   132  
   133  // elements must already be sorted!
   134  func TrimBy[T Number](elements []T, trim int) []T {
   135  	numberToKeep := int(float32(len(elements)) * (float32(trim) / 100.0))
   136  
   137  	return elements[:numberToKeep]
   138  }
   139  
   140  func TrimmedMean[T Number](elements []T, trim int) (float64, []T) {
   141  	sortedElements := make([]T, len(elements))
   142  	copy(sortedElements, elements)
   143  	slices.Sort(sortedElements)
   144  
   145  	trimmedElements := TrimBy(sortedElements, trim)
   146  	return CalculateAverage(trimmedElements), trimmedElements
   147  }