diff --git a/HdrHistogram.UnitTests/HistogramTestBase.cs b/HdrHistogram.UnitTests/HistogramTestBase.cs index f1d15fd..f07b7d1 100644 --- a/HdrHistogram.UnitTests/HistogramTestBase.cs +++ b/HdrHistogram.UnitTests/HistogramTestBase.cs @@ -259,6 +259,51 @@ public void Add_throws_if_other_has_a_larger_range() Assert.Throws(() => { histogram.Add(biggerOther); }); } + [Fact] + public void Subtract_should_reduce_the_counts_from_two_histograms() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + var other = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + histogram.RecordValue(TestValueLevel); + histogram.RecordValue(TestValueLevel); + histogram.RecordValue(TestValueLevel * 1000); + histogram.RecordValue(TestValueLevel * 1000); + other.RecordValue(TestValueLevel); + other.RecordValue(TestValueLevel * 1000); + + histogram.Subtract(other); + + Assert.Equal(1L, histogram.GetCountAtValue(TestValueLevel)); + Assert.Equal(1L, histogram.GetCountAtValue(TestValueLevel * 1000)); + Assert.Equal(2L, histogram.TotalCount); + } + + [Fact] + public void Subtract_should_allow_small_range_histograms_to_be_subtracted() + { + var biggerOther = Create(DefaultHighestTrackableValue * 2, DefaultSignificantFigures); + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + biggerOther.RecordValue(TestValueLevel); + biggerOther.RecordValue(TestValueLevel * 1000); + histogram.RecordValue(TestValueLevel); + histogram.RecordValue(TestValueLevel * 1000); + + biggerOther.Subtract(histogram); + + Assert.Equal(0L, biggerOther.GetCountAtValue(TestValueLevel)); + Assert.Equal(0L, biggerOther.GetCountAtValue(TestValueLevel * 1000)); + Assert.Equal(0L, biggerOther.TotalCount); + } + + [Fact] + public void Subtract_throws_if_other_has_a_larger_range() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + var biggerOther = Create(DefaultHighestTrackableValue * 2, DefaultSignificantFigures); + + Assert.Throws(() => { histogram.Subtract(biggerOther); }); + } + [Theory] [InlineData(1, 1)] [InlineData(2, 2500)] diff --git a/HdrHistogram/HistogramBase.cs b/HdrHistogram/HistogramBase.cs index 194fb48..0e90363 100644 --- a/HdrHistogram/HistogramBase.cs +++ b/HdrHistogram/HistogramBase.cs @@ -280,7 +280,7 @@ public virtual void Add(HistogramBase fromHistogram) { if (HighestTrackableValue < fromHistogram.HighestTrackableValue) { - throw new ArgumentOutOfRangeException(nameof(fromHistogram), $"The other histogram covers a wider range ({fromHistogram.HighestTrackableValue} than this one ({HighestTrackableValue})."); + throw new ArgumentOutOfRangeException(nameof(fromHistogram), $"The other histogram covers a wider range ({fromHistogram.HighestTrackableValue}) than this one ({HighestTrackableValue})."); } if ((BucketCount == fromHistogram.BucketCount) && (SubBucketCount == fromHistogram.SubBucketCount) && @@ -305,7 +305,42 @@ public virtual void Add(HistogramBase fromHistogram) } /// - /// Get the size (in value units) of the range of values that are equivalent to the given value within the histogram's resolution. + /// Subtract the contents of another histogram from this one. + /// + /// The other histogram. + /// if fromHistogram covers a wider range than this histogram. + public virtual void Subtract(HistogramBase fromHistogram) + { + if (HighestTrackableValue < fromHistogram.HighestTrackableValue) + { + throw new ArgumentOutOfRangeException( + nameof(fromHistogram), + $"The other histogram covers a wider range ({fromHistogram.HighestTrackableValue}) than this one ({HighestTrackableValue})."); + } + if ((BucketCount == fromHistogram.BucketCount) && + (SubBucketCount == fromHistogram.SubBucketCount) && + (_unitMagnitude == fromHistogram._unitMagnitude)) + { + // Counts arrays are of the same length and meaning, so we can just iterate and subtract directly: + for (var i = 0; i < fromHistogram.CountsArrayLength; i++) + { + AddToCountAtIndex(i, -fromHistogram.GetCountAtIndex(i)); + } + } + else + { + // Arrays are not a direct match, so we can't just stream through and subtract them. + // Instead, go through the array and subtract each non-zero value found at its proper value: + for (var i = 0; i < fromHistogram.CountsArrayLength; i++) + { + var count = fromHistogram.GetCountAtIndex(i); + RecordValueWithCount(fromHistogram.ValueFromIndex(i), -count); + } + } + } + + /// + /// Get the size (in value units) of the range of values that are equivalent to the given value within the histogram's resolution. /// Where "equivalent" means that value samples recorded for any two equivalent values are counted in a common total count. /// /// The given value diff --git a/spec/tech-standards/api-reference.md b/spec/tech-standards/api-reference.md index 1e8ee5e..dd57810 100644 --- a/spec/tech-standards/api-reference.md +++ b/spec/tech-standards/api-reference.md @@ -138,6 +138,7 @@ long GetCountBetweenValues(long lowValue, long highValue) ```csharp void Add(HistogramBase other) // Merge histograms +void Subtract(HistogramBase other) // Remove histogram values HistogramBase Copy() // Create a copy void Reset() // Clear all counts bool HasOverflowed() // Check for overflow