/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Assertions.h" #include "mozilla/CumulativeAverage.h" #include using mozilla::CumulativeAverage; class MyClass { public: double mValue; explicit MyClass(double aValue = 0.0) : mValue(aValue) {} bool operator==(const MyClass& aOther) const { return mValue == aOther.mValue; } MyClass operator-(const MyClass& aOther) const { return MyClass(mValue - aOther.mValue); } MyClass& operator+=(const MyClass& aOther) { mValue += aOther.mValue; return *this; } MyClass operator/(uint64_t aDiv) const { return MyClass(mValue / static_cast(aDiv)); } }; class CumulativeAverageSuite { public: CumulativeAverageSuite() = default; void runTests() { testEmpty(); testSingleInsert(); testReset(); testNumericalCorrectness(); testOppositeSignsNoIntermediateOverflow(); testSubnormalValues(); testClass(); testFloatNoIntermediateOverflow(); } private: void testEmpty() { CumulativeAverage avg; MOZ_RELEASE_ASSERT(avg.empty()); MOZ_RELEASE_ASSERT(avg.count() == 0); } void testSingleInsert() { CumulativeAverage avg; avg.insert(42.0); MOZ_RELEASE_ASSERT(!avg.empty()); MOZ_RELEASE_ASSERT(avg.count() == 1); MOZ_RELEASE_ASSERT(avg.mean() == 42.0); } void testReset() { CumulativeAverage avg; avg.insert(10.0); avg.reset(); MOZ_RELEASE_ASSERT(avg.empty()); MOZ_RELEASE_ASSERT(avg.count() == 0); avg.insert(5.0); MOZ_RELEASE_ASSERT(avg.mean() == 5.0); } void testNumericalCorrectness() { CumulativeAverage avg; avg.insert(10.0); avg.insert(20.0); avg.insert(30.0); MOZ_RELEASE_ASSERT(avg.count() == 3); MOZ_RELEASE_ASSERT(avg.mean() == 20.0); } void testOppositeSignsNoIntermediateOverflow() { constexpr double kMax = std::numeric_limits::max(); CumulativeAverage avg; avg.insert(kMax); avg.insert(-kMax); MOZ_RELEASE_ASSERT(avg.mean() == 0.0); avg.reset(); avg.insert(-kMax); avg.insert(kMax); MOZ_RELEASE_ASSERT(avg.mean() == 0.0); } void testSubnormalValues() { // Smallest representable (subnormal) magnitudes average correctly through // the normal recurrence; these do not reach the overflow guard. constexpr double kMin = std::numeric_limits::denorm_min(); CumulativeAverage avg; avg.insert(kMin); avg.insert(-kMin); MOZ_RELEASE_ASSERT(avg.mean() == 0.0); avg.reset(); avg.insert(-kMin); avg.insert(kMin); MOZ_RELEASE_ASSERT(avg.mean() == 0.0); } void testClass() { // Overflow protection applies only to built-in floating-point types. // If kMax is double's maximum, inserting MyClass(kMax) then MyClass(-kMax) // makes avg.mean().mValue negative infinity, not 0, because subtraction // overflows. CumulativeAverage avg; MOZ_RELEASE_ASSERT(avg.empty()); avg.insert(MyClass(4.0)); MOZ_RELEASE_ASSERT(avg.mean() == MyClass(4.0)); avg.reset(); MOZ_RELEASE_ASSERT(avg.empty()); } void testFloatNoIntermediateOverflow() { // The naive formula (mean * (n-1) + value) / n overflows float to infinity // once mean * n exceeds FLT_MAX (~3.4e38). CumulativeAverage never forms // that product, so it stays finite regardless of sample magnitude. CumulativeAverage avg; constexpr float kLarge = 1e38f; // 5 * kLarge > FLT_MAX for (int i = 0; i < 5; ++i) { avg.insert(kLarge); } MOZ_RELEASE_ASSERT(avg.mean() == kLarge); } }; int main() { CumulativeAverageSuite suite; suite.runTests(); return 0; }