]> git.ipfire.org Git - thirdparty/squid.git/blame - src/SquidMath.h
Source Format Enforcement (#1234)
[thirdparty/squid.git] / src / SquidMath.h
CommitLineData
bbc27441 1/*
b8ae064d 2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
bbc27441
AJ
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
a98bcbee
AJ
9#ifndef _SQUID_SRC_SQUIDMATH_H
10#define _SQUID_SRC_SQUIDMATH_H
11
72247610 12#include "base/forward.h"
e5ddd4ce 13#include "base/TypeTraits.h"
72247610
AJ
14
15#include <limits>
09835feb 16#include <optional>
72247610
AJ
17
18// TODO: Move to src/base/Math.h and drop the Math namespace
19
a98bcbee 20/* Math functions we define locally for Squid */
f9afa4fb
A
21namespace Math
22{
a98bcbee 23
8a648e8d
FC
24int intPercent(const int a, const int b);
25int64_t int64Percent(const int64_t a, const int64_t b);
26double doublePercent(const double, const double);
27int intAverage(const int, const int, int, const int);
28double doubleAverage(const double, const double, int, const int);
a98bcbee 29
e5519212 30} // namespace Math
a98bcbee 31
72247610
AJ
32// If Sum() performance becomes important, consider using GCC and clang
33// built-ins like __builtin_add_overflow() instead of manual overflow checks.
34
72247610 35/// detects a pair of unsigned types
b308d7e2 36/// reduces code duplication in declarations further below
72247610
AJ
37template <typename T, typename U>
38using AllUnsigned = typename std::conditional<
70ac5b29 39 std::is_unsigned<T>::value && std::is_unsigned<U>::value,
40 std::true_type,
41 std::false_type
42 >::type;
72247610 43
b308d7e2
AR
44// TODO: Replace with std::cmp_less() after migrating to C++20.
45/// whether integer a is less than integer b, with correct overflow handling
46template <typename A, typename B>
47constexpr bool
48Less(const A a, const B b) {
49 // The casts below make standard C++ integer conversions explicit. They
50 // quell compiler warnings about signed/unsigned comparison. The first two
51 // lines exclude different-sign a and b, making the casts/comparison safe.
52 using AB = typename std::common_type<A, B>::type;
53 return
54 (a >= 0 && b < 0) ? false :
55 (a < 0 && b >= 0) ? true :
56 /* (a >= 0) == (b >= 0) */ static_cast<AB>(a) < static_cast<AB>(b);
57}
58
59/// ensure that T is supported by NaturalSum() and friends
60template<typename T>
09835feb 61constexpr void
b308d7e2
AR
62AssertNaturalType()
63{
64 static_assert(std::numeric_limits<T>::is_bounded, "std::numeric_limits<T>::max() is meaningful");
65 static_assert(std::numeric_limits<T>::is_exact, "no silent loss of precision");
66 static_assert(!std::is_enum<T>::value, "no silent creation of non-enumerated values");
b308d7e2
AR
67}
68
69// TODO: Investigate whether this optimization can be expanded to [signed] types
70// A and B when std::numeric_limits<decltype(A(0)+B(0))>::is_modulo is true.
71/// This IncreaseSumInternal() overload is optimized for speed.
72247610 72/// \returns a non-overflowing sum of the two unsigned arguments (or nothing)
b308d7e2 73/// \prec both argument types are unsigned
09835feb
AR
74template <typename S, typename A, typename B, std::enable_if_t<AllUnsigned<A,B>::value, int> = 0>
75std::optional<S>
b308d7e2
AR
76IncreaseSumInternal(const A a, const B b) {
77 // paranoid: AllUnsigned<A,B> precondition established that already
78 static_assert(std::is_unsigned<A>::value, "AllUnsigned dispatch worked for A");
79 static_assert(std::is_unsigned<B>::value, "AllUnsigned dispatch worked for B");
80
09835feb
AR
81 AssertNaturalType<S>();
82 AssertNaturalType<A>();
83 AssertNaturalType<B>();
b308d7e2
AR
84
85 // we should only be called by IncreaseSum(); it forces integer promotion
86 static_assert(std::is_same<A, decltype(+a)>::value, "a will not be promoted");
87 static_assert(std::is_same<B, decltype(+b)>::value, "b will not be promoted");
88 // and without integer promotions, a sum of unsigned integers is unsigned
89 static_assert(std::is_unsigned<decltype(a+b)>::value, "a+b is unsigned");
90
91 // with integer promotions ruled out, a or b can only undergo integer
92 // conversion to the higher rank type (A or B, we do not know which)
93 using AB = typename std::common_type<A, B>::type;
94 static_assert(std::is_same<AB, A>::value || std::is_same<AB, B>::value, "no unexpected conversions");
95 static_assert(std::is_same<AB, decltype(a+b)>::value, "lossless assignment");
96 const AB sum = a + b;
97
98 static_assert(std::numeric_limits<AB>::is_modulo, "we can detect overflows");
99 // 1. modulo math: overflowed sum is smaller than any of its operands
100 // 2. the sum may overflow S (i.e. the return base type)
101 // We do not need Less() here because we compare promoted unsigned types.
102 return (sum >= a && sum <= std::numeric_limits<S>::max()) ?
09835feb 103 std::optional<S>(sum) : std::optional<S>();
72247610
AJ
104}
105
b308d7e2
AR
106/// This IncreaseSumInternal() overload supports a larger variety of types.
107/// \returns a non-overflowing sum of the two arguments (or nothing)
108/// \returns nothing if at least one of the arguments is negative
109/// \prec at least one of the argument types is signed
09835feb
AR
110template <typename S, typename A, typename B, std::enable_if_t<!AllUnsigned<A,B>::value, int> = 0>
111std::optional<S> constexpr
b308d7e2 112IncreaseSumInternal(const A a, const B b) {
09835feb
AR
113 AssertNaturalType<S>();
114 AssertNaturalType<A>();
115 AssertNaturalType<B>();
b308d7e2
AR
116
117 // we should only be called by IncreaseSum() that does integer promotion
118 static_assert(std::is_same<A, decltype(+a)>::value, "a will not be promoted");
119 static_assert(std::is_same<B, decltype(+b)>::value, "b will not be promoted");
120
121 return
122 // We could support a non-under/overflowing sum of negative numbers, but
123 // our callers use negative values specially (e.g., for do-not-use or
124 // do-not-limit settings) and are not supposed to do math with them.
09835feb 125 (a < 0 || b < 0) ? std::optional<S>() :
b308d7e2
AR
126 // To avoid undefined behavior of signed overflow, we must not compute
127 // the raw a+b sum if it may overflow. When A is not B, a or b undergoes
128 // (safe for non-negatives) integer conversion in these expressions, so
129 // we do not know the resulting a+b type AB and its maximum. We must
130 // also detect subsequent casting-to-S overflows.
131 // Overflow condition: (a + b > maxAB) or (a + b > maxS).
132 // A is an integer promotion of S, so maxS <= maxA <= maxAB.
133 // Since maxS <= maxAB, it is sufficient to just check: a + b > maxS,
134 // which is the same as the overflow-safe condition here: maxS - a < b.
135 // Finally, (maxS - a) cannot overflow because a is not negative and
136 // cannot underflow because a is a promotion of s: 0 <= a <= maxS.
09835feb
AR
137 Less(std::numeric_limits<S>::max() - a, b) ? std::optional<S>() :
138 std::optional<S>(a + b);
b308d7e2
AR
139}
140
141/// argument pack expansion termination for IncreaseSum<S, T, Args...>()
142template <typename S, typename T>
09835feb 143std::optional<S>
b308d7e2
AR
144IncreaseSum(const S s, const T t)
145{
09835feb 146 // Force (always safe) integer promotions now, to give std::enable_if_t<>
b308d7e2
AR
147 // promoted types instead of entering IncreaseSumInternal<AllUnsigned>(s,t)
148 // but getting a _signed_ promoted value of s or t in s + t.
149 return IncreaseSumInternal<S>(+s, +t);
72247610
AJ
150}
151
152/// \returns a non-overflowing sum of the arguments (or nothing)
b308d7e2 153template <typename S, typename T, typename... Args>
09835feb 154std::optional<S>
b308d7e2
AR
155IncreaseSum(const S sum, const T t, const Args... args) {
156 if (const auto head = IncreaseSum(sum, t)) {
157 return IncreaseSum(head.value(), args...);
72247610 158 } else {
09835feb
AR
159 // std::optional<S>() triggers bogus -Wmaybe-uninitialized warnings in GCC v10.3
160 return std::nullopt;
72247610
AJ
161 }
162}
163
b308d7e2
AR
164/// \returns an exact, non-overflowing sum of the arguments (or nothing)
165template <typename SummationType, typename... Args>
09835feb 166std::optional<SummationType>
b308d7e2
AR
167NaturalSum(const Args... args) {
168 return IncreaseSum<SummationType>(0, args...);
169}
170
171/// Safely resets the given variable to NaturalSum() of the given arguments.
172/// If the sum overflows, resets to variable's maximum possible value.
173/// \returns the new variable value (like an assignment operator would)
174template <typename S, typename... Args>
175S
176SetToNaturalSumOrMax(S &var, const Args... args)
177{
178 var = NaturalSum<S>(args...).value_or(std::numeric_limits<S>::max());
179 return var;
180}
181
d448e1eb
EB
182/// converts a given non-negative integer into an integer of a given type
183/// without loss of information or undefined behavior
184template <typename Result, typename Source>
185Result
186NaturalCast(const Source s)
187{
188 return NaturalSum<Result>(s).value();
189}
190
a98bcbee 191#endif /* _SQUID_SRC_SQUIDMATH_H */
f53969cc 192