/src/serenity/AK/Math/Rounding.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021-2026, Leon Albrecht <leon2002.la@gmail.com>. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/Concepts.h> |
10 | | #include <AK/Math/Division.h> |
11 | | #include <AK/NumericLimits.h> |
12 | | |
13 | | #include <AK/Math/Macros.h> |
14 | | |
15 | | namespace AK { |
16 | | |
17 | | namespace Rounding { |
18 | | template<FloatingPoint T> |
19 | | constexpr T ceil(T num) |
20 | 4.66k | { |
21 | | // FIXME: SSE4.1 rounds[sd] num, res, 0b110 |
22 | 4.66k | if (is_constant_evaluated()) { |
23 | 0 | if (num < NumericLimits<i64>::min() || num > NumericLimits<i64>::max()) |
24 | 0 | return num; |
25 | 0 | return (static_cast<T>(static_cast<i64>(num)) == num) |
26 | 0 | ? static_cast<i64>(num) |
27 | 0 | : static_cast<i64>(num) + ((num > 0) ? 1 : 0); |
28 | 0 | } |
29 | | #if ARCH(AARCH64) |
30 | | AARCH64_INSTRUCTION(frintp, num); |
31 | | #else |
32 | 4.66k | CALL_BUILTIN(ceil, num); |
33 | 4.66k | #endif |
34 | 4.66k | } _ZN2AK8Rounding4ceilITkNS_8Concepts13FloatingPointEfEET_S3_ Line | Count | Source | 20 | 4.66k | { | 21 | | // FIXME: SSE4.1 rounds[sd] num, res, 0b110 | 22 | 4.66k | if (is_constant_evaluated()) { | 23 | 0 | if (num < NumericLimits<i64>::min() || num > NumericLimits<i64>::max()) | 24 | 0 | return num; | 25 | 0 | return (static_cast<T>(static_cast<i64>(num)) == num) | 26 | 0 | ? static_cast<i64>(num) | 27 | 0 | : static_cast<i64>(num) + ((num > 0) ? 1 : 0); | 28 | 0 | } | 29 | | #if ARCH(AARCH64) | 30 | | AARCH64_INSTRUCTION(frintp, num); | 31 | | #else | 32 | 4.66k | CALL_BUILTIN(ceil, num); | 33 | 4.66k | #endif | 34 | 4.66k | } |
Unexecuted instantiation: _ZN2AK8Rounding4ceilITkNS_8Concepts13FloatingPointEdEET_S3_ |
35 | | |
36 | | template<FloatingPoint T> |
37 | | constexpr T floor(T num) |
38 | 0 | { |
39 | | // FIXME: SSE4.1 rounds[sd] num, res, 0b101 |
40 | 0 | if (is_constant_evaluated()) { |
41 | 0 | if (num < NumericLimits<i64>::min() || num > NumericLimits<i64>::max()) |
42 | 0 | return num; |
43 | 0 | return (static_cast<T>(static_cast<i64>(num)) == num) |
44 | 0 | ? static_cast<i64>(num) |
45 | 0 | : static_cast<i64>(num) - ((num > 0) ? 0 : 1); |
46 | 0 | } |
47 | | #if ARCH(AARCH64) |
48 | | AARCH64_INSTRUCTION(frintm, num); |
49 | | #else |
50 | 0 | CALL_BUILTIN(floor, num); |
51 | 0 | #endif |
52 | 0 | } |
53 | | |
54 | | template<FloatingPoint T> |
55 | | constexpr T trunc(T num) |
56 | | { |
57 | | #if ARCH(AARCH64) |
58 | | if (is_constant_evaluated()) { |
59 | | if (num < NumericLimits<i64>::min() || num > NumericLimits<i64>::max()) |
60 | | return num; |
61 | | return static_cast<T>(static_cast<i64>(num)); |
62 | | } |
63 | | AARCH64_INSTRUCTION(frintz, num); |
64 | | #endif |
65 | | // FIXME: Use dedicated instruction in the non constexpr case |
66 | | // SSE4.1: rounds[sd] %num, %res, 0b111 |
67 | | if (num < NumericLimits<i64>::min() || num > NumericLimits<i64>::max()) |
68 | | return num; |
69 | | return static_cast<T>(static_cast<i64>(num)); |
70 | | } |
71 | | |
72 | | template<FloatingPoint T> |
73 | | constexpr T rint(T x) |
74 | | { |
75 | | CONSTEXPR_STATE(rint, x); |
76 | | // Note: This does break tie to even |
77 | | // But the behavior of frndint/rounds[ds]/frintx can be configured |
78 | | // through the floating point control registers. |
79 | | // FIXME: We should decide if we rename this to allow us to get away from |
80 | | // the configurability "burden" rint has |
81 | | // this would make us use `rounds[sd] %num, %res, 0b100` |
82 | | // and `frintn` respectively, |
83 | | // no such guaranteed round exists for x87 `frndint` |
84 | | #if ARCH(X86_64) |
85 | | # ifdef __SSE4_1__ |
86 | | if constexpr (IsSame<T, double>) { |
87 | | T r; |
88 | | asm( |
89 | | "roundsd %1, %0" |
90 | | : "=x"(r) |
91 | | : "x"(x)); |
92 | | return r; |
93 | | } |
94 | | if constexpr (IsSame<T, float>) { |
95 | | T r; |
96 | | asm( |
97 | | "roundss %1, %0" |
98 | | : "=x"(r) |
99 | | : "x"(x)); |
100 | | return r; |
101 | | } |
102 | | # else |
103 | | asm( |
104 | | "frndint" |
105 | | : "+t"(x)); |
106 | | return x; |
107 | | # endif |
108 | | #elif ARCH(AARCH64) |
109 | | AARCH64_INSTRUCTION(frintx, x); |
110 | | #elif ARCH(RISCV64) |
111 | | if (__builtin_isnan(x)) |
112 | | return x; |
113 | | |
114 | | // Floating point values have a gap size of >= 1 for values above 2^mantissa_bits - 1. |
115 | | if (fabs(x) > FloatExtractor<T>::mantissa_max) |
116 | | return x; |
117 | | |
118 | | if constexpr (IsSame<T, float>) { |
119 | | i64 r; |
120 | | asm("fcvt.l.s %0, %1, dyn" |
121 | | : "=r"(r) |
122 | | : "f"(x)); |
123 | | return copysign(static_cast<float>(r), x); |
124 | | } |
125 | | if constexpr (IsSame<T, double>) { |
126 | | i64 r; |
127 | | asm("fcvt.l.d %0, %1, dyn" |
128 | | : "=r"(r) |
129 | | : "f"(x)); |
130 | | return copysign(static_cast<double>(r), x); |
131 | | } |
132 | | if constexpr (IsSame<T, long double>) |
133 | | TODO_RISCV64(); |
134 | | #endif |
135 | | TODO(); |
136 | | } |
137 | | |
138 | | template<FloatingPoint T> |
139 | | constexpr T round(T x) |
140 | 0 | { |
141 | 0 | CONSTEXPR_STATE(round, x); |
142 | 0 | // Note: This is break-tie-away-from-zero, so not the hw's understanding of |
143 | 0 | // "nearest", which would be towards even. |
144 | 0 | if (x == 0.) |
145 | 0 | return x; |
146 | 0 | if (x > 0.) |
147 | 0 | return floor(x + .5); |
148 | 0 | return ceil(x - .5); |
149 | 0 | } |
150 | | |
151 | | template<Integral I, FloatingPoint P> |
152 | | ALWAYS_INLINE I round_to(P value); |
153 | | |
154 | | #if ARCH(X86_64) |
155 | | template<Integral I> |
156 | | ALWAYS_INLINE I round_to(long double value) |
157 | | { |
158 | | // Note: fistps outputs into a signed integer location (i16, i32, i64), |
159 | | // so lets be nice and tell the compiler that. |
160 | | Conditional<sizeof(I) >= sizeof(i16), MakeSigned<I>, i16> ret; |
161 | | if constexpr (sizeof(I) == sizeof(i64)) { |
162 | | asm("fistpll %0" |
163 | | : "=m"(ret) |
164 | | : "t"(value) |
165 | | : "st"); |
166 | | } else if constexpr (sizeof(I) == sizeof(i32)) { |
167 | | asm("fistpl %0" |
168 | | : "=m"(ret) |
169 | | : "t"(value) |
170 | | : "st"); |
171 | | } else { |
172 | | asm("fistps %0" |
173 | | : "=m"(ret) |
174 | | : "t"(value) |
175 | | : "st"); |
176 | | } |
177 | | return static_cast<I>(ret); |
178 | | } |
179 | | |
180 | | template<Integral I> |
181 | | ALWAYS_INLINE I round_to(float value) |
182 | 150M | { |
183 | | // FIXME: round_to<u64> might will cause issues, aka the indefinite value being set, |
184 | | // if the value surpasses the i64 limit, even if the result could fit into an u64 |
185 | | // To solve this we would either need to detect that value or do a range check and |
186 | | // then do a more specialized conversion, which might include a division (which is expensive) |
187 | | if constexpr (sizeof(I) == sizeof(i64) || IsSame<I, u32>) { |
188 | | i64 ret; |
189 | | asm("cvtss2si %1, %0" |
190 | | : "=r"(ret) |
191 | | : "xm"(value)); |
192 | | return static_cast<I>(ret); |
193 | | } |
194 | 150M | i32 ret; |
195 | 150M | asm("cvtss2si %1, %0" |
196 | 150M | : "=r"(ret) |
197 | 150M | : "xm"(value)); |
198 | 150M | return static_cast<I>(ret); |
199 | 150M | } _ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralEhEET_f Line | Count | Source | 182 | 150M | { | 183 | | // FIXME: round_to<u64> might will cause issues, aka the indefinite value being set, | 184 | | // if the value surpasses the i64 limit, even if the result could fit into an u64 | 185 | | // To solve this we would either need to detect that value or do a range check and | 186 | | // then do a more specialized conversion, which might include a division (which is expensive) | 187 | | if constexpr (sizeof(I) == sizeof(i64) || IsSame<I, u32>) { | 188 | | i64 ret; | 189 | | asm("cvtss2si %1, %0" | 190 | | : "=r"(ret) | 191 | | : "xm"(value)); | 192 | | return static_cast<I>(ret); | 193 | | } | 194 | 150M | i32 ret; | 195 | 150M | asm("cvtss2si %1, %0" | 196 | 150M | : "=r"(ret) | 197 | 150M | : "xm"(value)); | 198 | 150M | return static_cast<I>(ret); | 199 | 150M | } |
_ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralEiEET_f Line | Count | Source | 182 | 15.4k | { | 183 | | // FIXME: round_to<u64> might will cause issues, aka the indefinite value being set, | 184 | | // if the value surpasses the i64 limit, even if the result could fit into an u64 | 185 | | // To solve this we would either need to detect that value or do a range check and | 186 | | // then do a more specialized conversion, which might include a division (which is expensive) | 187 | | if constexpr (sizeof(I) == sizeof(i64) || IsSame<I, u32>) { | 188 | | i64 ret; | 189 | | asm("cvtss2si %1, %0" | 190 | | : "=r"(ret) | 191 | | : "xm"(value)); | 192 | | return static_cast<I>(ret); | 193 | | } | 194 | 15.4k | i32 ret; | 195 | 15.4k | asm("cvtss2si %1, %0" | 196 | 15.4k | : "=r"(ret) | 197 | 15.4k | : "xm"(value)); | 198 | 15.4k | return static_cast<I>(ret); | 199 | 15.4k | } |
Unexecuted instantiation: _ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralEsEET_f |
200 | | |
201 | | template<Integral I> |
202 | | ALWAYS_INLINE I round_to(double value) |
203 | 0 | { |
204 | | // FIXME: round_to<u64> might will cause issues, aka the indefinite value being set, |
205 | | // if the value surpasses the i64 limit, even if the result could fit into an u64 |
206 | | // To solve this we would either need to detect that value or do a range check and |
207 | | // then do a more specialized conversion, which might include a division (which is expensive) |
208 | 0 | if constexpr (sizeof(I) == sizeof(i64) || IsSame<I, u32>) { |
209 | 0 | i64 ret; |
210 | 0 | asm("cvtsd2si %1, %0" |
211 | 0 | : "=r"(ret) |
212 | 0 | : "xm"(value)); |
213 | 0 | return static_cast<I>(ret); |
214 | 0 | } |
215 | 0 | i32 ret; |
216 | 0 | asm("cvtsd2si %1, %0" |
217 | 0 | : "=r"(ret) |
218 | 0 | : "xm"(value)); |
219 | 0 | return static_cast<I>(ret); |
220 | 0 | } Unexecuted instantiation: _ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralEiEET_d Unexecuted instantiation: _ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralElEET_d Unexecuted instantiation: _ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralEhEET_d Unexecuted instantiation: _ZN2AK8Rounding8round_toITkNS_8Concepts8IntegralEmEET_d |
221 | | |
222 | | #elif ARCH(AARCH64) |
223 | | template<SignedIntegral I> |
224 | | ALWAYS_INLINE I round_to(float value) |
225 | | { |
226 | | if constexpr (sizeof(I) <= sizeof(u32)) { |
227 | | i32 res; |
228 | | asm("fcvtns %w0, %s1" |
229 | | : "=r"(res) |
230 | | : "w"(value)); |
231 | | return static_cast<I>(res); |
232 | | } |
233 | | i64 res; |
234 | | asm("fcvtns %0, %s1" |
235 | | : "=r"(res) |
236 | | : "w"(value)); |
237 | | return static_cast<I>(res); |
238 | | } |
239 | | |
240 | | template<SignedIntegral I> |
241 | | ALWAYS_INLINE I round_to(double value) |
242 | | { |
243 | | if constexpr (sizeof(I) <= sizeof(u32)) { |
244 | | i32 res; |
245 | | asm("fcvtns %w0, %d1" |
246 | | : "=r"(res) |
247 | | : "w"(value)); |
248 | | return static_cast<I>(res); |
249 | | } |
250 | | i64 res; |
251 | | asm("fcvtns %0, %d1" |
252 | | : "=r"(res) |
253 | | : "w"(value)); |
254 | | return static_cast<I>(res); |
255 | | } |
256 | | |
257 | | template<UnsignedIntegral U> |
258 | | ALWAYS_INLINE U round_to(float value) |
259 | | { |
260 | | if constexpr (sizeof(U) <= sizeof(u32)) { |
261 | | u32 res; |
262 | | asm("fcvtnu %w0, %s1" |
263 | | : "=r"(res) |
264 | | : "w"(value)); |
265 | | return static_cast<U>(res); |
266 | | } |
267 | | i64 res; |
268 | | asm("fcvtnu %0, %s1" |
269 | | : "=r"(res) |
270 | | : "w"(value)); |
271 | | return static_cast<U>(res); |
272 | | } |
273 | | |
274 | | template<UnsignedIntegral U> |
275 | | ALWAYS_INLINE U round_to(double value) |
276 | | { |
277 | | if constexpr (sizeof(U) <= sizeof(u32)) { |
278 | | u32 res; |
279 | | asm("fcvtns %w0, %d1" |
280 | | : "=r"(res) |
281 | | : "w"(value)); |
282 | | return static_cast<U>(res); |
283 | | } |
284 | | i64 res; |
285 | | asm("fcvtns %0, %d1" |
286 | | : "=r"(res) |
287 | | : "w"(value)); |
288 | | return static_cast<U>(res); |
289 | | } |
290 | | |
291 | | #else |
292 | | template<Integral I, FloatingPoint P> |
293 | | ALWAYS_INLINE I round_to(P value) |
294 | | { |
295 | | if constexpr (IsSame<P, long double>) |
296 | | return static_cast<I>(__builtin_llrintl(value)); |
297 | | if constexpr (IsSame<P, double>) |
298 | | return static_cast<I>(__builtin_llrint(value)); |
299 | | if constexpr (IsSame<P, float>) |
300 | | return static_cast<I>(__builtin_llrintf(value)); |
301 | | } |
302 | | #endif |
303 | | |
304 | | } |
305 | | |
306 | | using Rounding::ceil; |
307 | | using Rounding::floor; |
308 | | using Rounding::rint; |
309 | | using Rounding::round; |
310 | | using Rounding::round_to; |
311 | | using Rounding::trunc; |
312 | | |
313 | | template<Integral I, typename T> |
314 | | constexpr I clamp_to(T value) |
315 | 0 | { |
316 | 0 | constexpr auto max = static_cast<T>(NumericLimits<I>::max()); |
317 | 0 | if constexpr (max > 0) { |
318 | 0 | if (value >= static_cast<T>(NumericLimits<I>::max())) |
319 | 0 | return NumericLimits<I>::max(); |
320 | 0 | } |
321 | | |
322 | 0 | constexpr auto min = static_cast<T>(NumericLimits<I>::min()); |
323 | 0 | if constexpr (min <= 0) { |
324 | 0 | if (value <= static_cast<T>(NumericLimits<I>::min())) |
325 | 0 | return NumericLimits<I>::min(); |
326 | 0 | } |
327 | | |
328 | | if constexpr (IsFloatingPoint<T>) |
329 | 0 | return round_to<I>(value); |
330 | | |
331 | 0 | return value; |
332 | 0 | } Unexecuted instantiation: _ZN2AK8clamp_toITkNS_8Concepts8IntegralEldEET_T0_ Unexecuted instantiation: _ZN2AK8clamp_toITkNS_8Concepts8IntegralElmEET_T0_ Unexecuted instantiation: _ZN2AK8clamp_toITkNS_8Concepts8IntegralEhdEET_T0_ Unexecuted instantiation: _ZN2AK8clamp_toITkNS_8Concepts8IntegralEilEET_T0_ Unexecuted instantiation: _ZN2AK8clamp_toITkNS_8Concepts8IntegralEifEET_T0_ Unexecuted instantiation: _ZN2AK8clamp_toITkNS_8Concepts8IntegralEidEET_T0_ |
333 | | |
334 | | // Wrap a to keep it in the range [-b, b]. |
335 | | template<typename T> |
336 | | constexpr T wrap_to_range(T a, IdentityType<T> b) |
337 | | { |
338 | | return fmod(fmod(a + b, 2 * b) + 2 * b, 2 * b) - b; |
339 | | } |
340 | | |
341 | | #include <AK/Math/UndefMacros.h> |
342 | | |
343 | | } |
344 | | |
345 | | #if USING_AK_GLOBALLY |
346 | | using AK::round_to; |
347 | | #endif |