forked from ClickHouse/ClickHouse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNumberTraits.h
235 lines (199 loc) · 8.56 KB
/
NumberTraits.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#pragma once
#include <type_traits>
#include <Core/Types.h>
namespace DB
{
/** Allows get the result type of the functions +, -, *, /, %, intDiv (integer division).
* The rules are different from those used in C++.
*/
namespace NumberTraits
{
struct Error {};
constexpr size_t max(size_t x, size_t y)
{
return x > y ? x : y;
}
constexpr size_t min(size_t x, size_t y)
{
return x < y ? x : y;
}
/// @note There's no auto scale to larger big integer, only for integral ones.
/// It's cause of (U)Int64 backward compatibility and very big performance penalties.
constexpr size_t nextSize(size_t size)
{
if (size < 8)
return size * 2;
return size;
}
template <bool is_signed, bool is_floating, size_t size>
struct Construct
{
using Type = Error;
};
template <> struct Construct<false, false, 1> { using Type = UInt8; };
template <> struct Construct<false, false, 2> { using Type = UInt16; };
template <> struct Construct<false, false, 4> { using Type = UInt32; };
template <> struct Construct<false, false, 8> { using Type = UInt64; };
template <> struct Construct<false, false, 16> { using Type = UInt128; };
template <> struct Construct<false, false, 32> { using Type = UInt256; };
template <> struct Construct<false, true, 1> { using Type = Float32; };
template <> struct Construct<false, true, 2> { using Type = Float32; };
template <> struct Construct<false, true, 4> { using Type = Float32; };
template <> struct Construct<false, true, 8> { using Type = Float64; };
template <> struct Construct<true, false, 1> { using Type = Int8; };
template <> struct Construct<true, false, 2> { using Type = Int16; };
template <> struct Construct<true, false, 4> { using Type = Int32; };
template <> struct Construct<true, false, 8> { using Type = Int64; };
template <> struct Construct<true, false, 16> { using Type = Int128; };
template <> struct Construct<true, false, 32> { using Type = Int256; };
template <> struct Construct<true, true, 1> { using Type = Float32; };
template <> struct Construct<true, true, 2> { using Type = Float32; };
template <> struct Construct<true, true, 4> { using Type = Float32; };
template <> struct Construct<true, true, 8> { using Type = Float64; };
/** The result of addition or multiplication is calculated according to the following rules:
* - if one of the arguments is floating-point, the result is a floating point, otherwise - the whole;
* - if one of the arguments is signed, the result is signed, otherwise it is unsigned;
* - the result contains more bits (not only meaningful) than the maximum in the arguments
* (for example, UInt8 + Int32 = Int64).
*/
template <typename A, typename B> struct ResultOfAdditionMultiplication
{
using Type = typename Construct<
is_signed_v<A> || is_signed_v<B>,
std::is_floating_point_v<A> || std::is_floating_point_v<B>,
nextSize(max(sizeof(A), sizeof(B)))>::Type;
};
template <typename A, typename B> struct ResultOfSubtraction
{
using Type = typename Construct<
true,
std::is_floating_point_v<A> || std::is_floating_point_v<B>,
nextSize(max(sizeof(A), sizeof(B)))>::Type;
};
/** When dividing, you always get a floating-point number.
*/
template <typename A, typename B> struct ResultOfFloatingPointDivision
{
using Type = Float64;
};
/** For integer division, we get a number with the same number of bits as in divisible.
*/
template <typename A, typename B> struct ResultOfIntegerDivision
{
using Type = typename Construct<
is_signed_v<A> || is_signed_v<B>,
false,
sizeof(A)>::Type;
};
/** Division with remainder you get a number with the same number of bits as in divisor,
* or larger in case of signed type.
*/
template <typename A, typename B> struct ResultOfModulo
{
static constexpr bool result_is_signed = is_signed_v<A>;
/// If modulo of division can yield negative number, we need larger type to accommodate it.
/// Example: toInt32(-199) % toUInt8(200) will return -199 that does not fit in Int8, only in Int16.
static constexpr size_t size_of_result = result_is_signed ? nextSize(sizeof(B)) : sizeof(B);
using Type0 = typename Construct<result_is_signed, false, size_of_result>::Type;
using Type = std::conditional_t<std::is_floating_point_v<A> || std::is_floating_point_v<B>, Float64, Type0>;
};
template <typename A, typename B> struct ResultOfModuloLegacy
{
using Type0 = typename Construct<is_signed_v<A> || is_signed_v<B>, false, sizeof(B)>::Type;
using Type = std::conditional_t<std::is_floating_point_v<A> || std::is_floating_point_v<B>, Float64, Type0>;
};
template <typename A> struct ResultOfNegate
{
using Type = typename Construct<
true,
std::is_floating_point_v<A>,
is_signed_v<A> ? sizeof(A) : nextSize(sizeof(A))>::Type;
};
template <typename A> struct ResultOfAbs
{
using Type = typename Construct<
false,
std::is_floating_point_v<A>,
sizeof(A)>::Type;
};
/** For bitwise operations, an integer is obtained with number of bits is equal to the maximum of the arguments.
*/
template <typename A, typename B> struct ResultOfBit
{
using Type = typename Construct<
is_signed_v<A> || is_signed_v<B>,
false,
std::is_floating_point_v<A> || std::is_floating_point_v<B> ? 8 : max(sizeof(A), sizeof(B))>::Type;
};
template <typename A> struct ResultOfBitNot
{
using Type = typename Construct<
is_signed_v<A>,
false,
sizeof(A)>::Type;
};
/** Type casting for `if` function:
* UInt<x>, UInt<y> -> UInt<max(x,y)>
* Int<x>, Int<y> -> Int<max(x,y)>
* Float<x>, Float<y> -> Float<max(x, y)>
* UInt<x>, Int<y> -> Int<max(x*2, y)>
* Float<x>, [U]Int<y> -> Float<max(x, y*2)>
* Decimal<x>, Decimal<y> -> Decimal<max(x,y)>
* UUID, UUID -> UUID
* UInt64 , Int<x> -> Error
* Float<x>, [U]Int64 -> Error
*/
template <typename A, typename B>
struct ResultOfIf
{
static constexpr bool has_float = std::is_floating_point_v<A> || std::is_floating_point_v<B>;
static constexpr bool has_integer = is_integer<A> || is_integer<B>;
static constexpr bool has_signed = is_signed_v<A> || is_signed_v<B>;
static constexpr bool has_unsigned = !is_signed_v<A> || !is_signed_v<B>;
static constexpr bool has_big_int = is_big_int_v<A> || is_big_int_v<B>;
static constexpr size_t max_size_of_unsigned_integer = max(is_signed_v<A> ? 0 : sizeof(A), is_signed_v<B> ? 0 : sizeof(B));
static constexpr size_t max_size_of_signed_integer = max(is_signed_v<A> ? sizeof(A) : 0, is_signed_v<B> ? sizeof(B) : 0);
static constexpr size_t max_size_of_integer = max(is_integer<A> ? sizeof(A) : 0, is_integer<B> ? sizeof(B) : 0);
static constexpr size_t max_size_of_float = max(std::is_floating_point_v<A> ? sizeof(A) : 0, std::is_floating_point_v<B> ? sizeof(B) : 0);
using ConstructedType = typename Construct<has_signed, has_float,
((has_float && has_integer && max_size_of_integer >= max_size_of_float)
|| (has_signed && has_unsigned && max_size_of_unsigned_integer >= max_size_of_signed_integer))
? max(sizeof(A), sizeof(B)) * 2
: max(sizeof(A), sizeof(B))>::Type;
using Type =
std::conditional_t<std::is_same_v<A, B>, A,
std::conditional_t<is_decimal<A> && is_decimal<B>,
std::conditional_t<(sizeof(A) > sizeof(B)), A, B>,
std::conditional_t<!is_decimal<A> && !is_decimal<B>,
ConstructedType, Error>>>;
};
/** Before applying operator `%` and bitwise operations, operands are casted to whole numbers. */
template <typename A> struct ToInteger
{
using Type = typename Construct<
is_signed_v<A>,
false,
std::is_floating_point_v<A> ? 8 : sizeof(A)>::Type;
};
// CLICKHOUSE-29. The same depth, different signs
// NOTE: This case is applied for 64-bit integers only (for backward compatibility), but could be used for any-bit integers
template <typename A, typename B>
constexpr bool LeastGreatestSpecialCase =
std::is_integral_v<A> && std::is_integral_v<B>
&& (8 == sizeof(A) && sizeof(A) == sizeof(B))
&& (is_signed_v<A> ^ is_signed_v<B>);
template <typename A, typename B>
using ResultOfLeast = std::conditional_t<LeastGreatestSpecialCase<A, B>,
typename Construct<true, false, sizeof(A)>::Type,
typename ResultOfIf<A, B>::Type>;
template <typename A, typename B>
using ResultOfGreatest = std::conditional_t<LeastGreatestSpecialCase<A, B>,
typename Construct<false, false, sizeof(A)>::Type,
typename ResultOfIf<A, B>::Type>;
}
template <typename T>
static inline auto littleBits(const T & x)
{
return static_cast<UInt8>(x);
}
}