Skip to content

Commit 3e09293

Browse files
committed
Adding fmean
it closes #73
1 parent fa0911d commit 3e09293

4 files changed

Lines changed: 115 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 1.1.4 - 2025-04-25
4+
- Adding `fmean()` method for computing the arithmetic mean with float numbers.
5+
36
## 1.1.3 - 2024-12-14
47
- Adding `multiply()` method to scale NormalDist by a constant
58

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,36 @@ $mean = Stat::mean([-1.0, 2.5, 3.25, 5.75]);
9393
// 2.625
9494
```
9595

96+
#### Stat::fmean( array $data, array|null $weights = null, int|null $precision = null )
97+
Return the arithmetic mean of the array `$data`, as a float, with optional weights and precision control.
98+
This function behaves like `mean()` but ensures a floating-point result and supports weighted datasets.
99+
If `$weights` is provided, it computes the weighted average. The result is rounded to a given decimal $precision.
100+
The result is rounded to `$precision` decimal places.
101+
If `$precision` is null, no rounding is applied — this may lead to results with long or unexpected decimal expansions due to the nature of floating-point arithmetic in PHP. Using rounding helps ensure cleaner, more predictable output.
102+
103+
```php
104+
use HiFolks\Statistics\Stat;
105+
106+
// Unweighted mean (same as mean but always float)
107+
$fmean = Stat::fmean([3.5, 4.0, 5.25]);
108+
// 4.25
109+
110+
// Weighted mean
111+
$fmean = Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1]);
112+
// 4.1875
113+
114+
// Custom precision
115+
$fmean = Stat::fmean([3.5, 4.0, 5.25], null, 2);
116+
// 4.25
117+
118+
$fmean = Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1], 3);
119+
// 4.188
120+
121+
```
122+
123+
If the input is empty, or weights are invalid (e.g., length mismatch or sum is zero), an exception is thrown.
124+
Use this function when you need floating-point accuracy or to apply custom weighting and rounding to your average.
125+
96126
#### Stat::geometricMean( array $data )
97127
The geometric mean indicates the central tendency or typical value of the data using the product of the values (as opposed to the arithmetic mean which uses their sum).
98128

src/Stat.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,60 @@ public static function mean(array $data): int|float|null
4949
return $sum / self::count($data);
5050
}
5151

52+
/**
53+
* Calculate the float number arithmetic mean of a float numbers dataset with optional weights and precision.
54+
*
55+
* Supports both unweighted and weighted means. Automatically casts values to float.
56+
* Returns `null` if the input data is empty.
57+
*
58+
* @param array<float> $data Array of floating numbers
59+
* @param null|array<float> $weights Optional array of weights (same length as $data).
60+
* @param null|int $precision Optional number of decimal places to round the result (default is null, no round() is applied).
61+
* @return float|null arithmetic mean
62+
*
63+
* @throws InvalidDataInputException if the data is empty
64+
*/
65+
public static function fmean(array $data, array|null $weights = null, int|null $precision = null): float|null
66+
{
67+
$sum = 0;
68+
$count = self::count($data);
69+
if ($count === 0) {
70+
throw new InvalidDataInputException('The data must not be empty.');
71+
}
72+
73+
// Unweighted mean
74+
if ($weights === null) {
75+
$sum = array_sum(array_map('floatval', $data));
76+
$count = count($data);
77+
if ($precision) {
78+
return round($sum / $count, $precision);
79+
}
80+
return $sum / $count;
81+
}
82+
83+
// Check lengths
84+
if ($count !== count($weights)) {
85+
throw new InvalidDataInputException('The data and weights must be the same length');
86+
}
87+
88+
$weightedSum = 0.0;
89+
$weightTotal = 0.0;
90+
foreach ($data as $i => $value) {
91+
$w = floatval($weights[$i]);
92+
$weightedSum += floatval($value) * $w;
93+
$weightTotal += $w;
94+
}
95+
96+
if ($weightTotal == 0) {
97+
throw new InvalidDataInputException('The sum of weights must be non-zero');
98+
}
99+
100+
if ($precision) {
101+
return round($weightedSum / $weightTotal, $precision);
102+
}
103+
return $weightedSum / $weightTotal;
104+
}
105+
52106
/**
53107
* Return the median (middle value) of data,
54108
* using the common “mean of middle two” method.

tests/StatTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,34 @@
1515
)->toThrow(InvalidDataInputException::class);
1616
});
1717

18+
it('calculates fmean (static)', function () {
19+
expect(
20+
Stat::mean([1, 2, 3, 4, 4]),
21+
)->toEqual(2.8);
22+
expect(
23+
Stat::mean([-1.0, 2.5, 3.25, 5.75]),
24+
)->toEqual(2.625);
25+
expect(
26+
fn() => Stat::mean([]),
27+
)->toThrow(InvalidDataInputException::class);
28+
expect(
29+
Stat::fmean([3.5, 4.0, 5.25]),
30+
)->toBeFloat()->toEqual(4.25);
31+
expect(
32+
Stat::fmean([85, 92, 83, 91], [0.20, 0.20, 0.30, 0.30], 2),
33+
)->toBeFloat()->toEqual(87.6);
34+
expect(
35+
Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1]),
36+
)->toBeFloat()->toEqual(4.1875);
37+
expect(
38+
Stat::fmean([3.5, 4.0, 5.25], precision: 2),
39+
)->toBeFloat()->toEqual(4.25);
40+
expect(
41+
Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1], precision: 3),
42+
)->toBeFloat()->toEqual(4.188);
43+
44+
});
45+
1846
it('calculates median (static)', function () {
1947
expect(
2048
Stat::median([1, 3, 5]),

0 commit comments

Comments
 (0)