• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Utils / 29770052588

20 Jul 2026 06:55PM UTC coverage: 92.619% (-0.1%) from 92.722%
29770052588

push

github

web-flow
Merge pull request #57 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

15 of 16 new or added lines in 7 files covered. (93.75%)

106 existing lines in 13 files now uncovered.

1669 of 1802 relevant lines covered (92.62%)

41.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.1
/src/Stats.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Utils.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Utils
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Utils;
18

19
/**
20
 * @see https://github.com/phpbench/phpbench/blob/master/lib/Math/Statistics.php
21
 * @psalm-suppress UnusedClass
22
 */
23
final class Stats
24
{
25
    /**
26
     * Returns the standard deviation of a given population.
27
     */
28
    public static function stdDev(array $values, bool $sample = false): float
29
    {
30
        $variance = self::variance($values, $sample);
30✔
31

32
        return \sqrt($variance);
30✔
33
    }
34

35
    /**
36
     * Returns the variance for a given population.
37
     */
38
    public static function variance(array $values, bool $sample = false): float
39
    {
40
        $average = self::mean($values);
30✔
41
        $sum     = 0;
30✔
42

43
        foreach ($values as $value) {
30✔
44
            $diff = ($value - $average) ** 2;
18✔
45
            $sum += $diff;
18✔
46
        }
47

48
        if (\count($values) === 0) {
30✔
49
            return 0;
18✔
50
        }
51

52
        return $sum / (\count($values) - ($sample ? 1 : 0));
18✔
53
    }
54

55
    /**
56
     * Returns the mean (average) value of the given values.
57
     */
58
    public static function mean(?array $values): float
59
    {
60
        if ($values === null || \count($values) === 0) {
36✔
61
            return 0;
24✔
62
        }
63

64
        \array_walk($values, static function (float|int|string|null &$value): void {
24✔
65
            $value = float($value);
24✔
66
        });
24✔
67
        $sum = \array_sum($values);
24✔
68

69
        if ($sum === 0.0) {
24✔
70
            return 0;
6✔
71
        }
72

73
        $count = \count($values);
24✔
74

75
        return \round($sum / $count, 9);
24✔
76
    }
77

78
    /**
79
     * Returns an array populated with $num numbers from $min to $max.
80
     * @return float[]
81
     */
82
    public static function linSpace(float $min, float $max, int $num = 50, bool $endpoint = true): array
83
    {
84
        $range = $max - $min;
6✔
85

86
        if ($max === $min) {
6✔
UNCOV
87
            throw new Exception("Min and max cannot be the same number: {$max}");
×
88
        }
89

90
        $unit  = $range / ($endpoint ? $num - 1 : $num);
6✔
91
        $space = [];
6✔
92

93
        for ($value = $min; $value <= $max; $value += $unit) {
6✔
94
            $space[] = $value;
6✔
95
        }
96

97
        if (!$endpoint) {
6✔
98
            \array_pop($space);
6✔
99
        }
100

101
        return $space;
6✔
102
    }
103

104
    /**
105
     * Generate a histogram. Note this is not a great function, and should not be relied upon for serious use.
106
     * @see http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.histogram.html
107
     * @param float[] $values
108
     */
109
    public static function histogram(
110
        array $values,
111
        int $steps = 10,
112
        ?float $lowerBound = null,
113
        ?float $upperBound = null,
114
    ): array {
115
        if (\count($values) === 0) {
6✔
UNCOV
116
            throw new Exception('Empty array of values is given');
×
117
        }
118

119
        $min = $lowerBound ?? \min($values);
6✔
120
        $max = $upperBound ?? \max($values);
6✔
121

122
        $range = $max - $min;
6✔
123

124
        $step = $range / $steps;
6✔
125
        $steps++; // add one extra step to catch the max value
6✔
126

127
        $histogram = [];
6✔
128

129
        $floor = $min;
6✔
130

131
        for ($i = 0; $i < $steps; $i++) {
6✔
132
            $ceil = $floor + $step;
6✔
133

134
            if (!isset($histogram[(string)$floor])) {
6✔
135
                $histogram[(string)$floor] = 0;
6✔
136
            }
137

138
            foreach ($values as $value) {
6✔
139
                if ($value >= $floor && $value < $ceil) {
6✔
140
                    $histogram[(string)$floor]++;
6✔
141
                }
142
            }
143

144
            $floor += $step;
6✔
145
        }
146

147
        return $histogram;
6✔
148
    }
149

150
    /**
151
     * Render human readable string of average value and system error.
152
     */
153
    public static function renderAverage(array $values, int $rounding = 3): string
154
    {
155
        $avg    = \number_format(\round(self::mean($values), $rounding), $rounding);
12✔
156
        $stdDev = \number_format(\round(self::stdDev($values), $rounding), $rounding);
12✔
157

158
        return "{$avg}±{$stdDev}";
12✔
159
    }
160

161
    /**
162
     * Render human readable string of average value and system error.
163
     */
164
    public static function renderMedian(array $values, int $rounding = 3): string
165
    {
166
        $median = \number_format(\round(self::median($values), $rounding), $rounding);
12✔
167
        $stdDev = \number_format(\round(self::stdDev($values), $rounding), $rounding);
12✔
168

169
        return "{$median}±{$stdDev}";
12✔
170
    }
171

172
    /**
173
     * Calculate the percentile of a given population.
174
     * @param float[]|int[] $data
175
     */
176
    public static function percentile(array $data, float|int $percentile = 95): float
177
    {
178
        $count = \count($data);
36✔
179
        if ($count === 0) {
36✔
180
            return 0;
18✔
181
        }
182

183
        $percent = $percentile / 100;
30✔
184
        if ($percent < 0 || $percent > 1) {
30✔
185
            throw new Exception("Percentile should be between 0 and 100, {$percentile} given");
12✔
186
        }
187

188
        $allIndex   = ($count - 1) * $percent;
18✔
189
        $intValue   = (int)$allIndex;
18✔
190
        $floatValue = $allIndex - $intValue;
18✔
191

192
        \sort($data, \SORT_NUMERIC);
18✔
193

194
        if ($intValue + 1 < $count) {
18✔
195
            $result = $data[$intValue] + ($data[$intValue + 1] - $data[$intValue]) * $floatValue;
18✔
196
        } else {
197
            $result = $data[$intValue];
12✔
198
        }
199

200
        return \round(float($result), 6);
18✔
201
    }
202

203
    /**
204
     * Calculate the median of a given population.
205
     * @param float[]|int[] $data
206
     */
207
    public static function median(array $data): float
208
    {
209
        return self::percentile($data, 50.0);
18✔
210
    }
211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc