1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "audio_utils_statistics_tests"
19 #include <audio_utils/Statistics.h>
20 
21 #include <random>
22 #include <stdio.h>
23 #include <gtest/gtest.h>
24 
25 // create uniform distribution
26 template <typename T, typename V>
initUniform(V & data,T rangeMin,T rangeMax)27 static void initUniform(V& data, T rangeMin, T rangeMax) {
28     const size_t count = data.capacity();
29     std::minstd_rand gen(count);
30     std::uniform_real_distribution<T> dis(rangeMin, rangeMax);
31 
32     // for_each works for scalars
33     for (auto& datum : data) {
34         android::audio_utils::for_each(datum, [&](T &value) { return value = dis(gen);});
35     }
36 }
37 
38 // create gaussian distribution
39 template <typename T, typename V>
initNormal(V & data,T mean,T stddev)40 static void initNormal(V& data, T mean, T stddev) {
41     const size_t count = data.capacity();
42     std::minstd_rand gen(count);
43 
44     // values near the mean are the most likely
45     // standard deviation affects the dispersion of generated values from the mean
46     std::normal_distribution<> dis{mean, stddev};
47 
48     // for_each works for scalars
49     for (auto& datum : data) {
50         android::audio_utils::for_each(datum, [&](T &value) { return value = dis(gen);});
51     }
52 }
53 
54 // Used to create compile-time reference constants for variance testing.
55 template <typename T>
56 class ConstexprStatistics {
57 public:
58     template <size_t N>
ConstexprStatistics(const T (& a)[N])59     explicit constexpr ConstexprStatistics(const T (&a)[N])
60         : mN{N}
61         , mMax{android::audio_utils::max(a)}
62         , mMin{android::audio_utils::min(a)}
63         , mMean{android::audio_utils::sum(a) / mN}
64         , mM2{android::audio_utils::sumSqDiff(a, mMean)}
65         , mPopVariance{mM2 / mN}
66         , mPopStdDev{android::audio_utils::sqrt_constexpr(mPopVariance)}
67         , mVariance{mM2 / (mN - 1)}
68         , mStdDev{android::audio_utils::sqrt_constexpr(mVariance)}
69     { }
70 
getN() const71     constexpr int64_t getN() const { return mN; }
getMin() const72     constexpr T getMin() const { return mMin; }
getMax() const73     constexpr T getMax() const { return mMax; }
getWeight() const74     constexpr double getWeight() const { return (double)mN; }
getMean() const75     constexpr double getMean() const { return mMean; }
getVariance() const76     constexpr double getVariance() const { return mVariance; }
getStdDev() const77     constexpr double getStdDev() const { return mStdDev; }
getPopVariance() const78     constexpr double getPopVariance() const { return mPopVariance; }
getPopStdDev() const79     constexpr double getPopStdDev() const { return mPopStdDev; }
80 
81 private:
82     const size_t mN;
83     const T mMax;
84     const T mMin;
85     const double mMean;
86     const double mM2;
87     const double mPopVariance;
88     const double mPopStdDev;
89     const double mVariance;
90     const double mStdDev;
91 };
92 
93 class StatisticsTest : public testing::TestWithParam<const char *>
94 {
95 };
96 
97 // find power of 2 that is small enough that it doesn't add to 1. due to finite mantissa.
98 template <typename T>
smallp2()99 constexpr T smallp2() {
100     T smallOne{};
101     for (smallOne = T{1.}; smallOne + T{1.} > T{1.}; smallOne *= T(0.5));
102     return smallOne;
103 }
104 
105 // Our near expectation is 16x the bit that doesn't fit the mantissa.
106 // this works so long as we add values close in exponent with each other
107 // realizing that errors accumulate as the sqrt of N (random walk, lln, etc).
108 #define TEST_EXPECT_NEAR(e, v) \
109     EXPECT_NEAR((e), (v), abs((e) * std::numeric_limits<decltype(e)>::epsilon() * 8))
110 
111 #define PRINT_AND_EXPECT_EQ(expected, expr) { \
112     auto value = (expr); \
113     printf("(%s): %s\n", #expr, std::to_string(value).c_str()); \
114     if ((expected) == (expected)) { EXPECT_EQ((expected), (value)); } \
115     EXPECT_EQ((expected) != (expected), (value) != (value)); /* nan check */\
116 }
117 
118 #define PRINT_AND_EXPECT_NEAR(expected, expr) { \
119     auto ref = (expected); \
120     auto value = (expr); \
121     printf("(%s): %s\n", #expr, std::to_string(value).c_str()); \
122     TEST_EXPECT_NEAR(ref, value); \
123 }
124 
125 template <typename T, typename S>
verify(const T & stat,const S & refstat)126 static void verify(const T &stat, const S &refstat) {
127     EXPECT_EQ(refstat.getN(), stat.getN());
128     EXPECT_EQ(refstat.getMin(), stat.getMin());
129     EXPECT_EQ(refstat.getMax(), stat.getMax());
130     TEST_EXPECT_NEAR(refstat.getWeight(), stat.getWeight());
131     TEST_EXPECT_NEAR(refstat.getMean(), stat.getMean());
132     TEST_EXPECT_NEAR(refstat.getVariance(), stat.getVariance());
133     TEST_EXPECT_NEAR(refstat.getStdDev(), stat.getStdDev());
134     TEST_EXPECT_NEAR(refstat.getPopVariance(), stat.getPopVariance());
135     TEST_EXPECT_NEAR(refstat.getPopStdDev(), stat.getPopStdDev());
136 }
137 
138 // Test against fixed reference
139 
TEST(StatisticsTest,high_precision_sums)140 TEST(StatisticsTest, high_precision_sums)
141 {
142     static const double simple[] = { 1., 2., 3. };
143 
144     double rssum = android::audio_utils::sum<double, double>(simple);
145     PRINT_AND_EXPECT_EQ(6., rssum);
146     double kssum =
147         android::audio_utils::sum<double, android::audio_utils::KahanSum<double>>(simple);
148     PRINT_AND_EXPECT_EQ(6., kssum);
149     double nmsum =
150         android::audio_utils::sum<double, android::audio_utils::NeumaierSum<double>>(simple);
151     PRINT_AND_EXPECT_EQ(6., nmsum);
152 
153     double rs{};
154     android::audio_utils::KahanSum<double> ks{};
155     android::audio_utils::NeumaierSum<double> ns{};
156 
157     // add 1.
158     rs += 1.;
159     ks += 1.;
160     ns += 1.;
161 
162     static constexpr double smallOne = std::numeric_limits<double>::epsilon() * 0.5;
163     // add lots of small values
164     static const int loop = 1000;
165     for (int i = 0; i < loop; ++i) {
166         rs += smallOne;
167         ks += smallOne;
168         ns += smallOne;
169     }
170 
171     // remove 1.
172     rs += -1.;
173     ks += -1.;
174     ns += -1.;
175 
176     const double totalAdded = smallOne * loop;
177     printf("totalAdded: %lg\n", totalAdded);
178     PRINT_AND_EXPECT_EQ(0., rs);            // normal count fails
179     PRINT_AND_EXPECT_EQ(totalAdded, ks);    // kahan succeeds
180     PRINT_AND_EXPECT_EQ(totalAdded, ns);    // neumaier succeeds
181 
182     // test case where kahan fails and neumaier method succeeds.
183     static const double tricky[] = { 1e100, 1., -1e100 };
184 
185     rssum = android::audio_utils::sum<double, double>(tricky);
186     PRINT_AND_EXPECT_EQ(0., rssum);
187     kssum = android::audio_utils::sum<double, android::audio_utils::KahanSum<double>>(tricky);
188     PRINT_AND_EXPECT_EQ(0., kssum);
189     nmsum = android::audio_utils::sum<double, android::audio_utils::NeumaierSum<double>>(tricky);
190     PRINT_AND_EXPECT_EQ(1., nmsum);
191 }
192 
TEST(StatisticsTest,minmax_bounds)193 TEST(StatisticsTest, minmax_bounds)
194 {
195     // range based min and max use iterator forms of min and max.
196 
197     static constexpr double one[] = { 1. };
198 
199     PRINT_AND_EXPECT_EQ(std::numeric_limits<double>::infinity(),
200             android::audio_utils::min(&one[0], &one[0]));
201 
202     PRINT_AND_EXPECT_EQ(-std::numeric_limits<double>::infinity(),
203             android::audio_utils::max(&one[0], &one[0]));
204 
205     static constexpr int un[] = { 1 };
206 
207     PRINT_AND_EXPECT_EQ(std::numeric_limits<int>::max(),
208             android::audio_utils::min(&un[0], &un[0]));
209 
210     PRINT_AND_EXPECT_EQ(std::numeric_limits<int>::min(),
211             android::audio_utils::max(&un[0], &un[0]));
212 
213     double nanarray[] = { nan(""), nan(""), nan("") };
214 
215     PRINT_AND_EXPECT_EQ(std::numeric_limits<double>::infinity(),
216             android::audio_utils::min(nanarray));
217 
218     PRINT_AND_EXPECT_EQ(-std::numeric_limits<double>::infinity(),
219             android::audio_utils::max(nanarray));
220 
221     android::audio_utils::Statistics<double> s(nanarray);
222 
223     PRINT_AND_EXPECT_EQ(std::numeric_limits<double>::infinity(),
224            s.getMin());
225 
226     PRINT_AND_EXPECT_EQ(-std::numeric_limits<double>::infinity(),
227             s.getMax());
228 }
229 
230 /*
231 TEST(StatisticsTest, sqrt_convergence)
232 {
233     union {
234         int i;
235         float f;
236     } u;
237 
238     for (int i = 0; i < INT_MAX; ++i) {
239         u.i = i;
240         const float f = u.f;
241         if (!android::audio_utils::isnan(f)) {
242             const float sf = android::audio_utils::sqrt(f);
243             if ((i & (1 << 16) - 1) == 0) {
244                 printf("i: %d  f:%f  sf:%f\n", i, f, sf);
245             }
246         }
247     }
248 }
249 */
250 
TEST(StatisticsTest,minmax_simple_array)251 TEST(StatisticsTest, minmax_simple_array)
252 {
253     static constexpr double ary[] = { -1.5, 1.5, -2.5, 2.5 };
254 
255     PRINT_AND_EXPECT_EQ(-2.5, android::audio_utils::min(ary));
256 
257     PRINT_AND_EXPECT_EQ(2.5, android::audio_utils::max(ary));
258 
259     static constexpr int ray[] = { -1, 1, -2, 2 };
260 
261     PRINT_AND_EXPECT_EQ(-2, android::audio_utils::min(ray));
262 
263     PRINT_AND_EXPECT_EQ(2, android::audio_utils::max(ray));
264 }
265 
TEST(StatisticsTest,sqrt)266 TEST(StatisticsTest, sqrt)
267 {
268     // check doubles
269     PRINT_AND_EXPECT_EQ(std::numeric_limits<double>::infinity(),
270             android::audio_utils::sqrt(std::numeric_limits<double>::infinity()));
271 
272     PRINT_AND_EXPECT_EQ(std::nan(""),
273             android::audio_utils::sqrt(-std::numeric_limits<double>::infinity()));
274 
275     PRINT_AND_EXPECT_NEAR(sqrt(std::numeric_limits<double>::epsilon()),
276             android::audio_utils::sqrt(std::numeric_limits<double>::epsilon()));
277 
278     PRINT_AND_EXPECT_EQ(3.,
279             android::audio_utils::sqrt(9.));
280 
281     PRINT_AND_EXPECT_EQ(0.,
282             android::audio_utils::sqrt(0.));
283 
284     PRINT_AND_EXPECT_EQ(std::nan(""),
285             android::audio_utils::sqrt(-1.));
286 
287     PRINT_AND_EXPECT_EQ(std::nan(""),
288             android::audio_utils::sqrt(std::nan("")));
289 
290     // check floats
291     PRINT_AND_EXPECT_EQ(std::numeric_limits<float>::infinity(),
292             android::audio_utils::sqrt(std::numeric_limits<float>::infinity()));
293 
294     PRINT_AND_EXPECT_EQ(std::nanf(""),
295             android::audio_utils::sqrt(-std::numeric_limits<float>::infinity()));
296 
297     PRINT_AND_EXPECT_NEAR(sqrtf(std::numeric_limits<float>::epsilon()),
298             android::audio_utils::sqrt(std::numeric_limits<float>::epsilon()));
299 
300     PRINT_AND_EXPECT_EQ(2.f,
301             android::audio_utils::sqrt(4.f));
302 
303     PRINT_AND_EXPECT_EQ(0.f,
304             android::audio_utils::sqrt(0.f));
305 
306     PRINT_AND_EXPECT_EQ(std::nanf(""),
307             android::audio_utils::sqrt(-1.f));
308 
309     PRINT_AND_EXPECT_EQ(std::nanf(""),
310             android::audio_utils::sqrt(std::nanf("")));
311 }
312 
TEST(StatisticsTest,stat_reference)313 TEST(StatisticsTest, stat_reference)
314 {
315     // fixed reference compile time constants.
316     static constexpr double data[] = {0.1, -0.1, 0.2, -0.3};
317     static constexpr ConstexprStatistics<double> rstat(data); // use alpha = 1.
318     static constexpr android::audio_utils::Statistics<double> stat{data};
319 
320     verify(stat, rstat);
321 }
322 
TEST(StatisticsTest,stat_variable_alpha)323 TEST(StatisticsTest, stat_variable_alpha)
324 {
325     constexpr size_t TEST_SIZE = 1 << 20;
326     std::vector<double> data(TEST_SIZE);
327     std::vector<double> alpha(TEST_SIZE);
328 
329     initUniform(data, -1., 1.);
330     initUniform(alpha, .95, .99);
331 
332     android::audio_utils::ReferenceStatistics<double> rstat;
333     android::audio_utils::Statistics<double> stat;
334 
335     static_assert(std::is_trivially_copyable<decltype(stat)>::value,
336         "basic statistics must be trivially copyable");
337 
338     for (size_t i = 0; i < TEST_SIZE; ++i) {
339         rstat.setAlpha(alpha[i]);
340         rstat.add(data[i]);
341 
342         stat.setAlpha(alpha[i]);
343         stat.add(data[i]);
344     }
345 
346     printf("statistics: %s\n", stat.toString().c_str());
347     printf("ref statistics: %s\n", rstat.toString().c_str());
348     verify(stat, rstat);
349 }
350 
TEST(StatisticsTest,stat_vector)351 TEST(StatisticsTest, stat_vector)
352 {
353     // for operator overloading...
354     using namespace android::audio_utils;
355 
356     using data_t = std::tuple<double, double>;
357     using covariance_t = std::tuple<double, double, double, double>;
358     using covariance_ut_t = std::tuple<double, double, double>;
359 
360     constexpr size_t TEST_SIZE = 1 << 20;
361     std::vector<data_t> data(TEST_SIZE);
362     // std::vector<double> alpha(TEST_SIZE);
363 
364     initUniform(data, -1., 1.);
365 
366     std::cout << "sample data[0]: " << data[0] << "\n";
367 
368     Statistics<data_t, data_t, data_t, double, double, innerProduct_scalar<data_t>> stat;
369     Statistics<data_t, data_t, data_t, double,
370             covariance_t, outerProduct_tuple<data_t>> stat_outer;
371     Statistics<data_t, data_t, data_t, double,
372             covariance_ut_t, outerProduct_UT_tuple<data_t>> stat_outer_ut;
373 
374     using pair_t = std::pair<double, double>;
375     std::vector<pair_t> pairs(TEST_SIZE);
376     initUniform(pairs, -1., 1.);
377     Statistics<pair_t, pair_t, pair_t, double, double, innerProduct_scalar<pair_t>> stat_pair;
378 
379     using array_t = std::array<double, 2>;
380     using array_covariance_ut_t = std::array<double, 3>;
381     std::vector<array_t> arrays(TEST_SIZE);
382     initUniform(arrays, -1., 1.);
383     Statistics<array_t, array_t, array_t, double,
384                double, innerProduct_scalar<array_t>> stat_array;
385     Statistics<array_t, array_t, array_t, double,
386                array_covariance_ut_t, outerProduct_UT_array<array_t>> stat_array_ut;
387 
388     for (size_t i = 0; i < TEST_SIZE; ++i) {
389         stat.add(data[i]);
390         stat_outer.add(data[i]);
391         stat_outer_ut.add(data[i]);
392         stat_pair.add(pairs[i]);
393         stat_array.add(arrays[i]);
394         stat_array_ut.add(arrays[i]);
395     }
396 
397 #if 0
398     // these aren't trivially copyable
399     static_assert(std::is_trivially_copyable<decltype(stat)>::value,
400         "tuple based inner product not trivially copyable");
401     static_assert(std::is_trivially_copyable<decltype(stat_outer)>::value,
402         "tuple based outer product not trivially copyable");
403     static_assert(std::is_trivially_copyable<decltype(stat_outer_ut)>::value,
404         "tuple based outer product not trivially copyable");
405 #endif
406     static_assert(std::is_trivially_copyable<decltype(stat_array)>::value,
407         "array based inner product not trivially copyable");
408     static_assert(std::is_trivially_copyable<decltype(stat_array_ut)>::value,
409         "array based inner product not trivially copyable");
410 
411     // inner product variance should be same as outer product diagonal sum
412     const double variance = stat.getPopVariance();
413     EXPECT_NEAR(variance,
414         std::get<0>(stat_outer.getPopVariance()) +
415         std::get<3>(stat_outer.getPopVariance()),
416         variance * std::numeric_limits<double>::epsilon() * 128);
417 
418     // outer product covariance should be identical
419     PRINT_AND_EXPECT_NEAR(std::get<1>(stat_outer.getPopVariance()),
420         std::get<2>(stat_outer.getPopVariance()));
421 
422     // upper triangular computation should be identical to outer product
423     PRINT_AND_EXPECT_NEAR(std::get<0>(stat_outer.getPopVariance()),
424         std::get<0>(stat_outer_ut.getPopVariance()));
425     PRINT_AND_EXPECT_NEAR(std::get<1>(stat_outer.getPopVariance()),
426         std::get<1>(stat_outer_ut.getPopVariance()));
427     PRINT_AND_EXPECT_NEAR(std::get<3>(stat_outer.getPopVariance()),
428         std::get<2>(stat_outer_ut.getPopVariance()));
429 
430     PRINT_AND_EXPECT_EQ(variance, stat_pair.getPopVariance());
431 
432     EXPECT_TRUE(equivalent(stat_array_ut.getPopVariance(), stat_outer_ut.getPopVariance()));
433 
434     printf("statistics_inner: %s\n", stat.toString().c_str());
435     printf("statistics_outer: %s\n", stat_outer.toString().c_str());
436     printf("statistics_outer_ut: %s\n", stat_outer_ut.toString().c_str());
437 }
438 
TEST(StatisticsTest,stat_linearfit)439 TEST(StatisticsTest, stat_linearfit)
440 {
441     using namespace android::audio_utils; // for operator overload
442     LinearLeastSquaresFit<double> fit;
443 
444     static_assert(std::is_trivially_copyable<decltype(fit)>::value,
445         "LinearLeastSquaresFit must be trivially copyable");
446 
447     using array_t = std::array<double, 2>;
448     array_t data{0.0, 1.5};
449 
450     for (size_t i = 0; i < 10; ++i) {
451         fit.add(data);
452         data = data + array_t{0.1, 0.2};
453     }
454 
455     // check the y line equation
456     {
457         double a, b, r2;
458         fit.computeYLine(a, b, r2);
459         printf("y line - a:%lf  b:%lf  r2:%lf\n", a, b, r2);
460         PRINT_AND_EXPECT_NEAR(1.5, a); // y intercept
461         PRINT_AND_EXPECT_NEAR(2.0, b); // y slope
462         PRINT_AND_EXPECT_NEAR(1.0, r2); // correlation coefficient.
463 
464         // check same as static variant
465         double ac, bc, r2c;
466         computeYLineFromStatistics(ac, bc, r2c,
467             std::get<0>(fit.getMean()), /* mean_x */
468             std::get<1>(fit.getMean()), /* mean_y */
469             std::get<0>(fit.getPopVariance()), /* var_x */
470             std::get<1>(fit.getPopVariance()), /* cov_xy */
471             std::get<2>(fit.getPopVariance())); /* var_y */
472 
473         EXPECT_EQ(a, ac);
474         EXPECT_EQ(b, bc);
475         EXPECT_EQ(r2, r2c);
476 
477         TEST_EXPECT_NEAR(1.9, fit.getYFromX(0.2));
478         TEST_EXPECT_NEAR(0.2, fit.getXFromY(1.9));
479         TEST_EXPECT_NEAR(1.0, fit.getR2());
480     }
481 
482     // check the x line equation
483     {
484         double a, b, r2;
485         fit.computeXLine(a, b, r2);
486         printf("x line - a:%lf  b:%lf  r2:%lf\n", a, b, r2);
487         PRINT_AND_EXPECT_NEAR(-0.75, a); // x intercept
488         PRINT_AND_EXPECT_NEAR(0.5, b); // x slope
489         PRINT_AND_EXPECT_NEAR(1.0, r2); // correlation coefficient.
490     }
491 }
492 
TEST(StatisticsTest,stat_linearfit_noise)493 TEST(StatisticsTest, stat_linearfit_noise)
494 {
495     using namespace android::audio_utils; // for operator overload
496     using array_t = std::array<double, 2>;
497     LinearLeastSquaresFit<double> fit;
498 
499     // We use 1000 steps for a linear line going from (0, 0) to (1, 1) as true data for
500     // our linear fit.
501     constexpr size_t ELEMENTS = 1000;
502     array_t incr{1. / ELEMENTS, 1. / ELEMENTS};
503 
504     // To simulate additive noise, we use a Gaussian with stddev of 1, and then scale
505     // achieve the desired stddev. We precompute our noise here (1000 of them).
506     std::vector<array_t> noise(ELEMENTS);
507     initNormal(noise, 0. /* mean */, 1. /* stddev */);
508 
509     for (int i = 0; i < 30; ++i) {
510         // We run through 30 trials, with noise stddev ranging from 0 to 1.
511         // The steps increment linearly from 0.001 to 0.01, linearly from 0.01 to 0.1, and
512         // linearly again from 0.1 to 1.0.
513         // 0.001, 0.002, ... 0.009, 0.01, 0.02, ....0.09, 0.1, 0.2, .... 1.0
514         const double stddev = (i <= 10) ? i / 1000. : (i <= 20) ? (i - 9) / 100. : (i - 19) / 10.;
515         fit.reset();
516 
517         for (size_t j = 0; j < ELEMENTS; ++j) {
518             array_t data = j * incr + noise[j] * stddev;
519             fit.add(data);
520         }
521 
522         double a, b, r2;
523         fit.computeYLine(a, b, r2);
524         printf("stddev: %lf y line - N:%lld a:%lf  b:%lf  r2:%lf\n",
525                 stddev, (long long) fit.getN(), a, b, r2);
526     }
527 }
528 
529 
TEST_P(StatisticsTest,stat_simple_char)530 TEST_P(StatisticsTest, stat_simple_char)
531 {
532     const char *param = GetParam();
533 
534     android::audio_utils::Statistics<char> stat(0.9);
535     android::audio_utils::ReferenceStatistics<char> rstat(0.9);
536 
537     // feed the string character by character to the statistics collectors.
538     for (size_t i = 0; param[i] != '\0'; ++i) {
539         stat.add(param[i]);
540         rstat.add(param[i]);
541     }
542 
543     printf("statistics for %s: %s\n", param, stat.toString().c_str());
544     printf("ref statistics for %s: %s\n", param, rstat.toString().c_str());
545     // verify that the statistics are the same
546     verify(stat, rstat);
547 }
548 
549 // find the variance of pet names as signed characters.
550 const char *pets[] = {"cat", "dog", "elephant", "mountain lion"};
551 INSTANTIATE_TEST_CASE_P(PetNameStatistics, StatisticsTest,
552                         ::testing::ValuesIn(pets));
553 
TEST(StatisticsTest,simple_stats)554 TEST(StatisticsTest, simple_stats)
555 {
556     simple_stats_t ss{};
557 
558     for (const double value : { -1., 1., 3.}) {
559         simple_stats_log(&ss, value);
560     }
561 
562     PRINT_AND_EXPECT_EQ(3., ss.last);
563     PRINT_AND_EXPECT_EQ(1., ss.mean);
564     PRINT_AND_EXPECT_EQ(-1., ss.min);
565     PRINT_AND_EXPECT_EQ(3., ss.max);
566     PRINT_AND_EXPECT_EQ(3, ss.n);
567 
568     char buffer[256];
569     simple_stats_to_string(&ss, buffer, sizeof(buffer));
570     printf("simple_stats: %s", buffer);
571 }
572