Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Histogram.cpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2021 Reactive Markets Limited
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#include "Histogram.hpp"
17
18#include <cmath>
19#include <stdexcept>
20
21namespace toolbox {
22inline namespace hdr {
23using namespace std;
24namespace {
25
26// Smallest power of 2 containing value.
28{
29 return __builtin_clzll(value);
30}
31
32int32_t get_sub_bucket_index(int64_t value, int32_t bucket_index, int32_t unit_magnitude) noexcept
33{
34 return value >> (bucket_index + unit_magnitude);
35}
36
38 int32_t unit_magnitude) noexcept
39{
40 int64_t smallest_untrackable_value{sub_bucket_count << unit_magnitude};
42 while (smallest_untrackable_value <= value) {
43 if (smallest_untrackable_value > numeric_limits<int64_t>::max() / 2) {
44 return buckets_needed + 1;
45 }
48 }
49 return buckets_needed;
50}
51
53 int32_t unit_magnitude) noexcept
54{
55 return int64_t{sub_bucket_index} << (bucket_index + unit_magnitude);
56}
57
58int32_t get_sub_bucket_half_count_magnitude(int64_t significant_figures) noexcept
59{
60 const double largest_value_with_single_unit_resolution{pow(10, significant_figures) * 2.0};
64}
65
66} // namespace
67
68BucketConfig::BucketConfig(int64_t lowest_trackable_value, int64_t highest_trackable_value,
69 int32_t significant_figures)
70: lowest_trackable_value{lowest_trackable_value}
71, highest_trackable_value{highest_trackable_value}
72, significant_figures{significant_figures}
73, unit_magnitude(floor(log(lowest_trackable_value) / log(2)))
74, sub_bucket_half_count_magnitude{get_sub_bucket_half_count_magnitude(significant_figures)}
75, sub_bucket_count(pow(2, sub_bucket_half_count_magnitude + 1))
76, sub_bucket_half_count{sub_bucket_count / 2}
77, sub_bucket_mask{int64_t{sub_bucket_count - 1} << unit_magnitude}
78, bucket_count{buckets_needed_to_cover_value(highest_trackable_value, sub_bucket_count,
79 unit_magnitude)}
80, counts_len{(bucket_count + 1) * (sub_bucket_count / 2)}
81{
82 if (lowest_trackable_value < 1) {
83 throw invalid_argument{"min value must be greater than zero"};
84 }
85 if (significant_figures < 1 || significant_figures > 5) {
86 throw invalid_argument{"significant figures must be between 1 and 5"};
87 }
88 if (lowest_trackable_value * 2 > highest_trackable_value) {
89 throw invalid_argument{"highest trackable value too small"};
90 }
91 if (unit_magnitude + sub_bucket_half_count_magnitude > 61) {
92 throw invalid_argument{"invalid magnitude"};
93 }
94}
95
96Histogram::Histogram(const BucketConfig& config)
97: lowest_trackable_value_{config.lowest_trackable_value}
98, highest_trackable_value_{config.highest_trackable_value}
99, significant_figures_{config.significant_figures}
100, unit_magnitude_{config.unit_magnitude}
101, sub_bucket_half_count_magnitude_{config.sub_bucket_half_count_magnitude}
102, sub_bucket_count_{config.sub_bucket_count}
103, sub_bucket_half_count_{config.sub_bucket_half_count}
104, sub_bucket_mask_{config.sub_bucket_mask}
105, bucket_count_{config.bucket_count}
106, normalizing_index_offset_{0}
107, min_value_{numeric_limits<int64_t>::max()}
108, max_value_{0}
109, total_count_{0}
110{
111 counts_.resize(config.counts_len);
112}
113
114Histogram::Histogram(int64_t lowest_trackable_value, int64_t highest_trackable_value,
115 int significant_figures)
116: Histogram{BucketConfig{lowest_trackable_value, highest_trackable_value, significant_figures}}
117{
118}
119
121{
122 if (count_at_index(0) > 0) {
123 return 0;
124 }
125 return non_zero_min();
126}
127
129{
130 if (max_value_ == 0) {
131 return 0;
132 }
133 return highest_equivalent_value(max_value_);
134}
135
137{
138 return lowest_equivalent_value(a) == lowest_equivalent_value(b);
139}
140
142{
143 const int32_t bucket_index{get_bucket_index(value)};
144 const int32_t sub_bucket_index{get_sub_bucket_index(value, bucket_index, unit_magnitude_)};
145 return value_from_index(bucket_index, sub_bucket_index, unit_magnitude_);
146}
147
149{
150 return next_non_equivalent_value(value) - 1;
151}
152
154{
155 return counts_get_normalised(counts_index_for(value));
156}
157
159{
160 return counts_get_normalised(index);
161}
162
164{
165 int32_t bucket_index{(index >> sub_bucket_half_count_magnitude_) - 1};
166 int32_t sub_bucket_index{(index & (sub_bucket_half_count_ - 1)) + sub_bucket_half_count_};
167
168 if (bucket_index < 0) {
169 sub_bucket_index -= sub_bucket_half_count_;
170 bucket_index = 0;
171 }
172 return value_from_index(bucket_index, sub_bucket_index, unit_magnitude_);
173}
174
176{
177 const int32_t bucket_index{get_bucket_index(value)};
178 const int32_t sub_bucket_index{get_sub_bucket_index(value, bucket_index, unit_magnitude_)};
179 const int32_t adjusted_bucket{(sub_bucket_index >= sub_bucket_count_) ? (bucket_index + 1)
180 : bucket_index};
181 return int64_t{1} << (unit_magnitude_ + adjusted_bucket);
182}
183
185{
186 return lowest_equivalent_value(value) + size_of_equivalent_value_range(value);
187}
188
190{
191 return lowest_equivalent_value(value) + (size_of_equivalent_value_range(value) >> 1);
192}
193
195{
196 return counts_[normalize_index(index)];
197}
198
200{
201 min_value_ = numeric_limits<int64_t>::max();
202 max_value_ = 0;
203 total_count_ = 0;
204 fill(counts_.begin(), counts_.end(), 0);
205}
206
208{
209 return record_values(value, 1);
210}
211
212bool Histogram::record_values(int64_t value, int64_t count) noexcept
213{
214 if (value < 0) {
215 return false;
216 }
217 const int32_t counts_index{counts_index_for(value)};
218 if (counts_index < 0 || counts_len() <= counts_index) {
219 return false;
220 }
221 counts_inc_normalised(counts_index, count);
222 update_min_max(value);
223 return true;
224}
225
226int32_t Histogram::normalize_index(int32_t index) const noexcept
227{
228 if (normalizing_index_offset_ == 0) {
229 return index;
230 }
231
232 int32_t normalized_index{index - normalizing_index_offset_};
233
235 if (normalized_index < 0) {
236 adjustment = counts_len();
237 } else if (normalized_index >= counts_len()) {
238 adjustment = -counts_len();
239 }
241}
242
243int32_t Histogram::get_bucket_index(int64_t value) const noexcept
244{
245 // Smallest power of 2 containing value.
246 const int32_t pow2ceiling{64 - count_leading_zeros_64(value | sub_bucket_mask_)};
247 return pow2ceiling - unit_magnitude_ - (sub_bucket_half_count_magnitude_ + 1);
248}
249
250int32_t Histogram::counts_index(int32_t bucket_index, int32_t sub_bucket_index) const noexcept
251{
252 // Calculate the index for the first entry in the bucket.
253 // The following is the equivalent of ((bucket_index + 1) * subBucketHalfCount)).
254 const int32_t bucket_base_index{(bucket_index + 1) << sub_bucket_half_count_magnitude_};
255 // Calculate the offset in the bucket.
256 const int32_t offset_in_bucket{sub_bucket_index - sub_bucket_half_count_};
257 // The following is the equivalent of
258 // (sub_bucket_index - subBucketHalfCount) + bucketBaseIndex).
260}
261
262int32_t Histogram::counts_index_for(int64_t value) const noexcept
263{
264 const int32_t bucket_index{get_bucket_index(value)};
265 const int32_t sub_bucket_index{get_sub_bucket_index(value, bucket_index, unit_magnitude_)};
266 return counts_index(bucket_index, sub_bucket_index);
267}
268
269int64_t Histogram::non_zero_min() const noexcept
270{
271 if (min_value_ == numeric_limits<int64_t>::max()) {
272 return min_value_;
273 }
274 return lowest_equivalent_value(min_value_);
275}
276
277void Histogram::counts_inc_normalised(int32_t index, int64_t value) noexcept
278{
279 const int32_t normalised_index{normalize_index(index)};
280 counts_[normalised_index] += value;
281 total_count_ += value;
282}
283
284void Histogram::update_min_max(int64_t value) noexcept
285{
286 if (value != 0) {
287 min_value_ = std::min(min_value_, value);
288 }
289 max_value_ = std::max(max_value_, value);
290}
291
292} // namespace hdr
293} // namespace toolbox
A High Dynamic Range (HDR) Histogram.
Definition Histogram.hpp:64
Histogram(const BucketConfig &config)
Definition Histogram.cpp:96
std::int64_t next_non_equivalent_value(std::int64_t value) const noexcept
bool values_are_equivalent(std::int64_t a, std::int64_t b) const noexcept
std::int64_t value_at_index(std::int32_t index) const noexcept
std::int64_t highest_equivalent_value(std::int64_t value) const noexcept
std::int64_t counts_get_normalised(std::int32_t index) const noexcept
std::int64_t count_at_value(std::int64_t value) const noexcept
bool record_values(std::int64_t value, std::int64_t count) noexcept
void reset() noexcept
bool record_value(std::int64_t value) noexcept
std::int64_t count_at_index(std::int32_t index) const noexcept
std::int64_t size_of_equivalent_value_range(std::int64_t value) const noexcept
std::int64_t median_equivalent_value(std::int64_t value) const noexcept
std::int64_t max() const noexcept
Get maximum value from the histogram. Will return 0 if the histogram is empty.
std::int64_t lowest_equivalent_value(std::int64_t value) const noexcept
std::int64_t min() const noexcept
Get minimum value from the histogram. Will return 2^63-1 if the histogram is empty.
STL namespace.
int64_t max(const Histogram &h) noexcept
Definition Utility.cpp:46
constexpr std::size_t ceil(std::size_t dividend, std::size_t divisor) noexcept
Definition Math.hpp:150
constexpr auto bind() noexcept
Definition Slot.hpp:92
Bucket configuration.
Definition Histogram.hpp:27
BucketConfig(std::int64_t lowest_trackable_value, std::int64_t highest_trackable_value, int significant_figures)