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 }