Data-DDSketch-Shared
view release on metacpan or search on metacpan
t/01-basic.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use Data::DDSketch::Shared;
# constructor + introspection
{
my $dd = Data::DDSketch::Shared->new(undef, 0.01, 1024);
isa_ok $dd, 'Data::DDSketch::Shared';
cmp_ok abs($dd->alpha - 0.01), '<', 1e-9, 'alpha';
is $dd->num_buckets, 1024, 'num_buckets';
cmp_ok abs($dd->gamma - (1.01/0.99)), '<', 1e-12, 'gamma == (1+a)/(1-a)';
is $dd->count, 0, 'empty: count 0';
is $dd->zero_count, 0, 'empty: zero_count 0';
ok !defined($dd->quantile(0.5)), 'empty: quantile undef';
ok !defined($dd->min), 'empty: min undef';
ok !defined($dd->max), 'empty: max undef';
ok !defined($dd->mean), 'empty: mean undef';
is $dd->sum, 0, 'empty: sum 0';
}
# quantile accuracy: relative error <= alpha on a uniform distribution
{
my $alpha = 0.01;
my $dd = Data::DDSketch::Shared->new(undef, $alpha);
my $N = 10000;
$dd->add($_) for 1 .. $N;
is $dd->count, $N, 'count == number added';
is $dd->min, 1, 'min exact';
is $dd->max, $N, 'max exact';
cmp_ok abs($dd->mean - ($N + 1) / 2), '<', 1e-6, 'mean exact';
is $dd->sum, $N * ($N + 1) / 2, 'sum exact';
my $worst = 0;
for my $q (0.001, 0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99, 0.999) {
my $est = $dd->quantile($q);
my $true = int($q * ($N - 1)) + 1; # nearest-rank true value
my $rel = abs($est - $true) / $true;
$worst = $rel if $rel > $worst;
ok $rel <= $alpha * 1.001, sprintf("q=%.3f: rel err %.4f <= alpha", $q, $rel)
or diag "est=$est true=$true";
}
diag sprintf("worst relative error: %.5f (alpha=%.3f)", $worst, $alpha);
# quantile endpoints
cmp_ok abs($dd->quantile(0) - 1) / 1, '<=', $alpha * 1.001, 'q=0 ~ min';
cmp_ok abs($dd->quantile(1) - $N) / $N, '<=', $alpha * 1.001, 'q=1 ~ max';
# median convenience
is $dd->median, $dd->quantile(0.5), 'median == quantile(0.5)';
}
# negatives and zeros
{
my $dd = Data::DDSketch::Shared->new(undef, 0.01);
$dd->add($_) for -1000 .. -1;
$dd->add(0) for 1 .. 50;
$dd->add($_) for 1 .. 1000;
is $dd->count, 2050, 'count includes negatives and zeros';
is $dd->zero_count, 50, 'zero_count';
is $dd->min, -1000, 'min is the most negative';
is $dd->max, 1000, 'max is the most positive';
# symmetric distribution -> median is 0 (the zero bucket sits in the middle)
is $dd->quantile(0.5), 0, 'median of a symmetric set with a zero mass is 0';
cmp_ok $dd->quantile(0.1), '<', 0, 'low quantile is negative';
cmp_ok $dd->quantile(0.9), '>', 0, 'high quantile is positive';
# negatives keep the relative-error guarantee (mirror of positives)
cmp_ok abs($dd->quantile(0.9) + $dd->quantile(0.1)), '<', 1, 'symmetric quantiles cancel';
}
# add returns the running count; add_many
{
my $dd = Data::DDSketch::Shared->new(undef, 0.02);
is $dd->add(5), 1, 'add returns 1 for the first value';
is $dd->add(7), 2, 'add returns the running count';
my $n = $dd->add_many([ 1 .. 100 ]);
is $n, 100, 'add_many returns the batch size';
is $dd->count, 102, 'count after add_many';
# add_many matches a loop of add
my $a = Data::DDSketch::Shared->new(undef, 0.02);
my $b = Data::DDSketch::Shared->new(undef, 0.02);
$a->add_many([ map { $_ * 1.5 } 1 .. 200 ]);
$b->add($_ * 1.5) for 1 .. 200;
is $a->quantile(0.5), $b->quantile(0.5), 'add_many == a loop of add (same quantile)';
}
# clear
{
my $dd = Data::DDSketch::Shared->new(undef, 0.01);
$dd->add($_) for 1 .. 100;
$dd->clear;
is $dd->count, 0, 'clear resets count';
is $dd->zero_count, 0, 'clear resets zero_count';
ok !defined($dd->quantile(0.5)), 'clear -> quantile undef';
ok !defined($dd->min), 'clear -> min undef';
# usable after clear
$dd->add(42);
cmp_ok abs($dd->quantile(0.5) - 42) / 42, '<=', 0.011, 'usable after clear';
}
# merge: same geometry combines the distributions
{
my $a = Data::DDSketch::Shared->new(undef, 0.01, 2048);
my $b = Data::DDSketch::Shared->new(undef, 0.01, 2048);
$a->add($_) for 1 .. 5000;
$b->add($_) for 5001 .. 10000;
( run in 2.247 seconds using v1.01-cache-2.11-cpan-cb04b646d8c )