Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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