x/exp/stats: use ~float64 per proposal spec

Updates golang/go#69264

Change-Id: I8842c5e747984c56e13b5d120a8760313bdc710a
Reviewed-on: https://go-review.googlesource.com/c/exp/+/688795
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/stats/stats.go b/stats/stats.go
index bc3e2ff..c6803c2 100644
--- a/stats/stats.go
+++ b/stats/stats.go
@@ -40,15 +40,15 @@
 //
 // If values contains NaN or both Inf and -Inf, it returns NaN.
 // If values contains Inf, it returns Inf. If values contains -Inf, it returns -Inf.
-func Mean(values []float64) float64 {
+func Mean[F ~float64](values []F) F {
 	mean, infs := meanInf(values)
 	switch infs {
 	case negInf:
-		return math.Inf(-1)
+		return F(math.Inf(-1))
 	case posInf:
-		return math.Inf(1)
+		return F(math.Inf(1))
 	case negInf | posInf:
-		return math.NaN()
+		return F(math.NaN())
 	default: // passthrough mean or NaN
 	}
 	return mean
@@ -66,44 +66,44 @@
 // If values contains both Inf and -Inf, it returns NaN, Inf.
 // If values contains Inf, it returns Inf, Inf.
 // If values contains -Inf, it returns -Inf, Inf.
-func MeanAndStdDev(values []float64) (float64, float64) {
+func MeanAndStdDev[F ~float64](values []F) (F, F) {
 	mean, infs := meanInf(values)
 	switch infs {
 	case 0:
-		if math.IsNaN(mean) {
-			return mean, math.NaN()
+		if math.IsNaN(float64(mean)) {
+			return mean, F(math.NaN())
 		}
 	case negInf, posInf, negInf | posInf:
-		return mean, math.Inf(1)
+		return mean, F(math.Inf(1))
 	}
 	if len(values) == 1 {
 		return mean, 0
 	}
-	squaredDiffs := 0.0
+	squaredDiffs := F(0.0)
 	for _, v := range values {
 		diff := v - mean
 		squaredDiffs += diff * diff
 	}
-	return mean, math.Sqrt(squaredDiffs / float64(len(values)-1))
+	return mean, F(math.Sqrt(float64(squaredDiffs) / float64(len(values)-1)))
 }
 
 // meanInf calculates a naive mean value
 // and reports the infinities status.
-func meanInf(values []float64) (float64, infinities) {
+func meanInf[F ~float64](values []F) (F, infinities) {
 	if len(values) == 0 {
 		panic("mean: empty slice")
 	}
-	sum, infs := 0.0, infinities(0)
+	sum, infs := F(0.0), infinities(0)
 	for _, v := range values {
 		switch {
-		case math.IsInf(v, 1):
+		case math.IsInf(float64(v), 1):
 			infs |= posInf
-		case math.IsInf(v, -1):
+		case math.IsInf(float64(v), -1):
 			infs |= negInf
 		}
 		sum += v
 	}
-	return sum / float64(len(values)), infs
+	return F(sum / F(len(values))), infs
 }
 
 // infinities is a bitset that records the presence of ±Inf in the input
@@ -126,7 +126,7 @@
 // -Inf is treated as smaller than all other values,
 // Inf is treated as larger than all other values, and
 // -0.0 is treated as smaller than 0.0.
-func Median(values []float64) float64 { return Quantiles(values, 0.5)[0] }
+func Median[F ~float64](values []F) F { return Quantiles(values, 0.5)[0] }
 
 // Quantiles returns a sequence of quantiles of values.
 //
@@ -148,7 +148,7 @@
 // -Inf is treated as smaller than all other values,
 // Inf is treated as larger than all other values, and
 // -0.0 is treated as smaller than 0.0.
-func Quantiles(values []float64, quantiles ...float64) []float64 {
+func Quantiles[F ~float64](values []F, quantiles ...F) []F {
 	if len(values) == 0 {
 		panic("quantiles: empty slice")
 	}
@@ -156,10 +156,10 @@
 		values = slices.Clone(values)
 		slices.Sort(values)
 	}
-	res := make([]float64, len(quantiles))
-	if math.IsNaN(values[0]) {
+	res := make([]F, len(quantiles))
+	if math.IsNaN(float64(values[0])) {
 		for i := range res {
-			res[i] = math.NaN()
+			res[i] = F(math.NaN())
 		}
 		return res
 	}
@@ -182,14 +182,14 @@
 // over a sorted slice of vals.
 //
 // hyndmanFanR7 does not modify the array.
-func hyndmanFanR7(values []float64, q float64) float64 {
-	h := float64(len(values)-1)*q + 1
+func hyndmanFanR7[F ~float64](values []F, q F) F {
+	h := F(len(values)-1)*q + 1
 	// the h-th smallest of len(vals) values is at fn(h)-1.
-	return values[floor(h-1)] + (h-math.Floor(h))*(values[ceil(h-1)]-values[floor(h-1)])
+	return values[floor(h-1)] + (h-F(math.Floor(float64(h))))*(values[ceil(h-1)]-values[floor(h-1)])
 }
 
 // ceil returns the integer value of [math.Ceil].
-func ceil(n float64) int { return int(math.Ceil(n)) }
+func ceil[F ~float64](n F) int { return int(math.Ceil(float64(n))) }
 
 // floor returns the integer value of [math.Floor].
-func floor(n float64) int { return int(math.Floor(n)) }
+func floor[F ~float64](n F) int { return int(math.Floor(float64(n))) }