/src/serenity/AK/Math/Exponentials.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/BuiltinWrappers.h> |
10 | | #include <AK/Concepts.h> |
11 | | #include <AK/FloatingPoint.h> |
12 | | #include <AK/Math/Constants.h> |
13 | | #include <AK/Math/Fabs.h> |
14 | | #include <AK/NumericLimits.h> |
15 | | |
16 | | #include <AK/Math/Macros.h> |
17 | | |
18 | | namespace AK { |
19 | | |
20 | | namespace Exponentials { |
21 | | |
22 | | template<FloatingPoint T> |
23 | | constexpr T log2(T x) |
24 | 4.13M | { |
25 | 4.13M | CONSTEXPR_STATE(log2, x); |
26 | | |
27 | 4.13M | #if ARCH(X86_64) |
28 | | if constexpr (IsSame<T, long double>) { |
29 | | T ret; |
30 | | asm( |
31 | | "fld1\n" |
32 | | "fxch %%st(1)\n" |
33 | | "fyl2x\n" |
34 | | : "=t"(ret) |
35 | | : "0"(x)); |
36 | | return ret; |
37 | | } |
38 | 4.13M | #endif |
39 | | // References: |
40 | | // Gist comparing some implementations |
41 | | // * https://gist.github.com/Hendiadyoin1/f58346d66637deb9156ef360aa158bf9 |
42 | | |
43 | 4.13M | if (x == 0) |
44 | 0 | return -Infinity<T>; |
45 | 4.13M | if (x <= 0 || __builtin_isnan(x)) |
46 | 0 | return NaN<T>; |
47 | 4.13M | if (__builtin_isinf(x)) |
48 | 0 | return Infinity<T>; |
49 | | |
50 | 4.13M | auto ext = FloatExtractor<T>::from_float(x); |
51 | | |
52 | 4.13M | if (ext.exponent == 0) { |
53 | | // Denormal. log2(mantissa * 2 ** (-exponent_bias)) == log2(mantissa) - exponent_bias. |
54 | | // Shift mantissa until we have an implicit leading 1. ext.mantissa is always != 0 here. |
55 | | // Mantissa is mantissa_bits long, count_leading_zeroes() counts in ComponentType, adjust: |
56 | 0 | auto const always_zero_bits = sizeof(typename FloatExtractor<T>::ComponentType) * 8 - (FloatExtractor<T>::mantissa_bits); |
57 | 0 | unsigned leading_mantissa_zeroes = count_leading_zeroes(ext.mantissa) - always_zero_bits; |
58 | |
|
59 | 0 | int exponent = -FloatExtractor<T>::exponent_bias - leading_mantissa_zeroes; |
60 | 0 | ext.mantissa <<= leading_mantissa_zeroes + 1; |
61 | 0 | ext.exponent = FloatExtractor<T>::exponent_bias; |
62 | 0 | return exponent + log2(ext.to_float()); |
63 | 0 | } |
64 | | |
65 | | // When the mantissa shows 0b00 (implicitly 1.0) we are on a power of 2 |
66 | 4.13M | T exponent = ext.exponent - FloatExtractor<T>::exponent_bias; |
67 | 4.13M | if (ext.mantissa == 0) |
68 | 3.65M | return exponent; |
69 | | |
70 | 476k | FloatExtractor<T> mantissa_ext { |
71 | 476k | .mantissa = ext.mantissa, |
72 | 476k | .exponent = FloatExtractor<T>::exponent_bias, |
73 | 476k | .sign = ext.sign |
74 | 476k | }; |
75 | | |
76 | | // (1 <= mantissa < 2) |
77 | 476k | T m = mantissa_ext.to_float(); |
78 | | |
79 | | // This is a reconstruction of one of Sun's algorithms |
80 | | // They use a transformation to lower the problem space, |
81 | | // while keeping the precision, and a 14th degree polynomial, |
82 | | // which is mirrored at sqrt(2) |
83 | | // TODO: Sun has some more algorithms for this and other math functions, |
84 | | // leveraging LUTs, investigate those, if they are better in performance and/or precision. |
85 | | // These seem to be related to crLibM's implementations, for which papers and references |
86 | | // are available. |
87 | | // FIXME: Dynamically adjust the amount of precision between floats and doubles |
88 | | // AKA floats might need less accuracy here, in comparison to doubles |
89 | | |
90 | 476k | bool inverted = false; |
91 | | // m > sqrt(2) |
92 | 476k | if (m > Sqrt2<T>) { |
93 | 162k | inverted = true; |
94 | 162k | m = 2 / m; |
95 | 162k | } |
96 | 476k | T s = (m - 1) / (m + 1); |
97 | | // ln((1 + s) / (1 - s)) == ln(m) |
98 | 476k | T s2 = s * s; |
99 | | // clang-format off |
100 | 476k | T high_approx = s2 * (static_cast<T>(0.6666666666666735130) |
101 | 476k | + s2 * (static_cast<T>(0.3999999999940941908) |
102 | 476k | + s2 * (static_cast<T>(0.2857142874366239149) |
103 | 476k | + s2 * (static_cast<T>(0.2222219843214978396) |
104 | 476k | + s2 * (static_cast<T>(0.1818357216161805012) |
105 | 476k | + s2 * (static_cast<T>(0.1531383769920937332) |
106 | 476k | + s2 * static_cast<T>(0.1479819860511658591))))))); |
107 | | // clang-format on |
108 | | |
109 | | // ln(m) == 2 * s + s * high_approx |
110 | | // log2(m) == log2(e) * ln(m) |
111 | 476k | T log2_mantissa = L2_E<T> * (2 * s + s * high_approx); |
112 | 476k | if (inverted) |
113 | 162k | log2_mantissa = 1 - log2_mantissa; |
114 | 476k | return exponent + log2_mantissa; |
115 | 4.13M | } _ZN2AK12Exponentials4log2ITkNS_8Concepts13FloatingPointEfEET_S3_ Line | Count | Source | 24 | 4.13M | { | 25 | 4.13M | CONSTEXPR_STATE(log2, x); | 26 | | | 27 | 4.13M | #if ARCH(X86_64) | 28 | | if constexpr (IsSame<T, long double>) { | 29 | | T ret; | 30 | | asm( | 31 | | "fld1\n" | 32 | | "fxch %%st(1)\n" | 33 | | "fyl2x\n" | 34 | | : "=t"(ret) | 35 | | : "0"(x)); | 36 | | return ret; | 37 | | } | 38 | 4.13M | #endif | 39 | | // References: | 40 | | // Gist comparing some implementations | 41 | | // * https://gist.github.com/Hendiadyoin1/f58346d66637deb9156ef360aa158bf9 | 42 | | | 43 | 4.13M | if (x == 0) | 44 | 0 | return -Infinity<T>; | 45 | 4.13M | if (x <= 0 || __builtin_isnan(x)) | 46 | 0 | return NaN<T>; | 47 | 4.13M | if (__builtin_isinf(x)) | 48 | 0 | return Infinity<T>; | 49 | | | 50 | 4.13M | auto ext = FloatExtractor<T>::from_float(x); | 51 | | | 52 | 4.13M | if (ext.exponent == 0) { | 53 | | // Denormal. log2(mantissa * 2 ** (-exponent_bias)) == log2(mantissa) - exponent_bias. | 54 | | // Shift mantissa until we have an implicit leading 1. ext.mantissa is always != 0 here. | 55 | | // Mantissa is mantissa_bits long, count_leading_zeroes() counts in ComponentType, adjust: | 56 | 0 | auto const always_zero_bits = sizeof(typename FloatExtractor<T>::ComponentType) * 8 - (FloatExtractor<T>::mantissa_bits); | 57 | 0 | unsigned leading_mantissa_zeroes = count_leading_zeroes(ext.mantissa) - always_zero_bits; | 58 | |
| 59 | 0 | int exponent = -FloatExtractor<T>::exponent_bias - leading_mantissa_zeroes; | 60 | 0 | ext.mantissa <<= leading_mantissa_zeroes + 1; | 61 | 0 | ext.exponent = FloatExtractor<T>::exponent_bias; | 62 | 0 | return exponent + log2(ext.to_float()); | 63 | 0 | } | 64 | | | 65 | | // When the mantissa shows 0b00 (implicitly 1.0) we are on a power of 2 | 66 | 4.13M | T exponent = ext.exponent - FloatExtractor<T>::exponent_bias; | 67 | 4.13M | if (ext.mantissa == 0) | 68 | 3.65M | return exponent; | 69 | | | 70 | 476k | FloatExtractor<T> mantissa_ext { | 71 | 476k | .mantissa = ext.mantissa, | 72 | 476k | .exponent = FloatExtractor<T>::exponent_bias, | 73 | 476k | .sign = ext.sign | 74 | 476k | }; | 75 | | | 76 | | // (1 <= mantissa < 2) | 77 | 476k | T m = mantissa_ext.to_float(); | 78 | | | 79 | | // This is a reconstruction of one of Sun's algorithms | 80 | | // They use a transformation to lower the problem space, | 81 | | // while keeping the precision, and a 14th degree polynomial, | 82 | | // which is mirrored at sqrt(2) | 83 | | // TODO: Sun has some more algorithms for this and other math functions, | 84 | | // leveraging LUTs, investigate those, if they are better in performance and/or precision. | 85 | | // These seem to be related to crLibM's implementations, for which papers and references | 86 | | // are available. | 87 | | // FIXME: Dynamically adjust the amount of precision between floats and doubles | 88 | | // AKA floats might need less accuracy here, in comparison to doubles | 89 | | | 90 | 476k | bool inverted = false; | 91 | | // m > sqrt(2) | 92 | 476k | if (m > Sqrt2<T>) { | 93 | 162k | inverted = true; | 94 | 162k | m = 2 / m; | 95 | 162k | } | 96 | 476k | T s = (m - 1) / (m + 1); | 97 | | // ln((1 + s) / (1 - s)) == ln(m) | 98 | 476k | T s2 = s * s; | 99 | | // clang-format off | 100 | 476k | T high_approx = s2 * (static_cast<T>(0.6666666666666735130) | 101 | 476k | + s2 * (static_cast<T>(0.3999999999940941908) | 102 | 476k | + s2 * (static_cast<T>(0.2857142874366239149) | 103 | 476k | + s2 * (static_cast<T>(0.2222219843214978396) | 104 | 476k | + s2 * (static_cast<T>(0.1818357216161805012) | 105 | 476k | + s2 * (static_cast<T>(0.1531383769920937332) | 106 | 476k | + s2 * static_cast<T>(0.1479819860511658591))))))); | 107 | | // clang-format on | 108 | | | 109 | | // ln(m) == 2 * s + s * high_approx | 110 | | // log2(m) == log2(e) * ln(m) | 111 | 476k | T log2_mantissa = L2_E<T> * (2 * s + s * high_approx); | 112 | 476k | if (inverted) | 113 | 162k | log2_mantissa = 1 - log2_mantissa; | 114 | 476k | return exponent + log2_mantissa; | 115 | 4.13M | } |
Unexecuted instantiation: _ZN2AK12Exponentials4log2ITkNS_8Concepts13FloatingPointEdEET_S3_ Unexecuted instantiation: _ZN2AK12Exponentials4log2ITkNS_8Concepts13FloatingPointEdEET_S3_ |
116 | | |
117 | | template<FloatingPoint T> |
118 | | constexpr T log(T x) |
119 | 48 | { |
120 | 48 | CONSTEXPR_STATE(log, x); |
121 | | |
122 | 48 | #if ARCH(X86_64) |
123 | 48 | T ret; |
124 | 48 | asm( |
125 | 48 | "fldln2\n" |
126 | 48 | "fxch %%st(1)\n" |
127 | 48 | "fyl2x\n" |
128 | 48 | : "=t"(ret) |
129 | 48 | : "0"(x)); |
130 | 48 | return ret; |
131 | | #elif defined(AK_OS_SERENITY) |
132 | | // FIXME: Adjust the polynomial and formula in log2 to fit this |
133 | | return log2<T>(x) / L2_E<T>; |
134 | | #else |
135 | | CALL_BUILTIN(log, x); |
136 | | #endif |
137 | 48 | } |
138 | | |
139 | | template<FloatingPoint T> |
140 | | constexpr T log10(T x) |
141 | | { |
142 | | CONSTEXPR_STATE(log10, x); |
143 | | |
144 | | #if ARCH(X86_64) |
145 | | T ret; |
146 | | asm( |
147 | | "fldlg2\n" |
148 | | "fxch %%st(1)\n" |
149 | | "fyl2x\n" |
150 | | : "=t"(ret) |
151 | | : "0"(x)); |
152 | | return ret; |
153 | | #elif defined(AK_OS_SERENITY) |
154 | | // FIXME: Adjust the polynomial and formula in log2 to fit this |
155 | | return log2<T>(x) / L2_10<T>; |
156 | | #else |
157 | | CALL_BUILTIN(log10, x); |
158 | | #endif |
159 | | } |
160 | | |
161 | | template<FloatingPoint T> |
162 | | constexpr T exp2(T exponent) |
163 | 4.13M | { |
164 | 4.13M | CONSTEXPR_STATE(exp2, exponent); |
165 | | |
166 | 4.13M | #if ARCH(X86_64) |
167 | 4.13M | T res; |
168 | 4.13M | asm("fld1\n" |
169 | 4.13M | "fld %%st(1)\n" |
170 | 4.13M | "fprem\n" |
171 | 4.13M | "f2xm1\n" |
172 | 4.13M | "faddp\n" |
173 | 4.13M | "fscale\n" |
174 | 4.13M | "fstp %%st(1)" |
175 | 4.13M | : "=t"(res) |
176 | 4.13M | : "0"(exponent)); |
177 | 4.13M | return res; |
178 | | #else |
179 | | // TODO: Add better implementation of this function. |
180 | | // This is just fast exponentiation for the integer part and |
181 | | // the first couple terms of the taylor series for the fractional part. |
182 | | |
183 | | if (exponent < 0) |
184 | | return 1 / exp2(-exponent); |
185 | | |
186 | | if (exponent >= log2(NumericLimits<T>::max())) |
187 | | return Infinity<T>; |
188 | | |
189 | | // Integer exponentiation part. |
190 | | int int_exponent = static_cast<int>(exponent); |
191 | | T exponent_fraction = exponent - int_exponent; |
192 | | |
193 | | T int_result = 1; |
194 | | T base = 2; |
195 | | for (;;) { |
196 | | if (int_exponent & 1) |
197 | | int_result *= base; |
198 | | int_exponent >>= 1; |
199 | | if (!int_exponent) |
200 | | break; |
201 | | base *= base; |
202 | | } |
203 | | |
204 | | // Fractional part. |
205 | | // Uses: |
206 | | // exp(x) = sum(n, 0, \infty, x ** n / n!) |
207 | | // 2**x = exp(log2(e) * x) |
208 | | // FIXME: Pick better step size (and make it dependent on T). |
209 | | T result = 0; |
210 | | T power = 1; |
211 | | T factorial = 1; |
212 | | for (int i = 1; i < 16; ++i) { |
213 | | result += power / factorial; |
214 | | power *= exponent_fraction / L2_E<T>; |
215 | | factorial *= i; |
216 | | } |
217 | | |
218 | | return int_result * result; |
219 | | #endif |
220 | 4.13M | } _ZN2AK12Exponentials4exp2ITkNS_8Concepts13FloatingPointEfEET_S3_ Line | Count | Source | 163 | 4.13M | { | 164 | 4.13M | CONSTEXPR_STATE(exp2, exponent); | 165 | | | 166 | 4.13M | #if ARCH(X86_64) | 167 | 4.13M | T res; | 168 | 4.13M | asm("fld1\n" | 169 | 4.13M | "fld %%st(1)\n" | 170 | 4.13M | "fprem\n" | 171 | 4.13M | "f2xm1\n" | 172 | 4.13M | "faddp\n" | 173 | 4.13M | "fscale\n" | 174 | 4.13M | "fstp %%st(1)" | 175 | 4.13M | : "=t"(res) | 176 | 4.13M | : "0"(exponent)); | 177 | 4.13M | return res; | 178 | | #else | 179 | | // TODO: Add better implementation of this function. | 180 | | // This is just fast exponentiation for the integer part and | 181 | | // the first couple terms of the taylor series for the fractional part. | 182 | | | 183 | | if (exponent < 0) | 184 | | return 1 / exp2(-exponent); | 185 | | | 186 | | if (exponent >= log2(NumericLimits<T>::max())) | 187 | | return Infinity<T>; | 188 | | | 189 | | // Integer exponentiation part. | 190 | | int int_exponent = static_cast<int>(exponent); | 191 | | T exponent_fraction = exponent - int_exponent; | 192 | | | 193 | | T int_result = 1; | 194 | | T base = 2; | 195 | | for (;;) { | 196 | | if (int_exponent & 1) | 197 | | int_result *= base; | 198 | | int_exponent >>= 1; | 199 | | if (!int_exponent) | 200 | | break; | 201 | | base *= base; | 202 | | } | 203 | | | 204 | | // Fractional part. | 205 | | // Uses: | 206 | | // exp(x) = sum(n, 0, \infty, x ** n / n!) | 207 | | // 2**x = exp(log2(e) * x) | 208 | | // FIXME: Pick better step size (and make it dependent on T). | 209 | | T result = 0; | 210 | | T power = 1; | 211 | | T factorial = 1; | 212 | | for (int i = 1; i < 16; ++i) { | 213 | | result += power / factorial; | 214 | | power *= exponent_fraction / L2_E<T>; | 215 | | factorial *= i; | 216 | | } | 217 | | | 218 | | return int_result * result; | 219 | | #endif | 220 | 4.13M | } |
Unexecuted instantiation: _ZN2AK12Exponentials4exp2ITkNS_8Concepts13FloatingPointEdEET_S3_ Unexecuted instantiation: _ZN2AK12Exponentials4exp2ITkNS_8Concepts13FloatingPointEdEET_S3_ |
221 | | |
222 | | template<FloatingPoint T> |
223 | | constexpr T exp(T exponent) |
224 | 0 | { |
225 | 0 | CONSTEXPR_STATE(exp, exponent); |
226 | |
|
227 | 0 | #if ARCH(X86_64) |
228 | 0 | T res; |
229 | 0 | asm("fldl2e\n" |
230 | 0 | "fmulp\n" |
231 | 0 | "fld1\n" |
232 | 0 | "fld %%st(1)\n" |
233 | 0 | "fprem\n" |
234 | 0 | "f2xm1\n" |
235 | 0 | "faddp\n" |
236 | 0 | "fscale\n" |
237 | 0 | "fstp %%st(1)" |
238 | 0 | : "=t"(res) |
239 | 0 | : "0"(exponent)); |
240 | 0 | return res; |
241 | | #else |
242 | | // TODO: Add better implementation of this function. |
243 | | return exp2(exponent * L2_E<T>); |
244 | | #endif |
245 | 0 | } Unexecuted instantiation: _ZN2AK12Exponentials3expITkNS_8Concepts13FloatingPointEfEET_S3_ Unexecuted instantiation: _ZN2AK12Exponentials3expITkNS_8Concepts13FloatingPointEfEET_S3_ |
246 | | |
247 | | } |
248 | | |
249 | | using Exponentials::exp; |
250 | | using Exponentials::exp2; |
251 | | using Exponentials::log; |
252 | | using Exponentials::log10; |
253 | | using Exponentials::log2; |
254 | | |
255 | | // Calculate x^y with fast exponentiation when the power is a natural number. |
256 | | template<FloatingPoint F, UnsignedIntegral U> |
257 | | constexpr F pow_int(F x, U y) |
258 | 126k | { |
259 | 126k | auto result = static_cast<F>(1); |
260 | 760k | while (y > 0) { |
261 | 633k | if (y % 2 == 1) { |
262 | 403k | result *= x; |
263 | 403k | } |
264 | 633k | x = x * x; |
265 | 633k | y >>= 1; |
266 | 633k | } |
267 | 126k | return result; |
268 | 126k | } _ZN2AK7pow_intITkNS_8Concepts13FloatingPointEfTkNS1_16UnsignedIntegralEmEET_S2_T0_ Line | Count | Source | 258 | 126k | { | 259 | 126k | auto result = static_cast<F>(1); | 260 | 760k | while (y > 0) { | 261 | 633k | if (y % 2 == 1) { | 262 | 403k | result *= x; | 263 | 403k | } | 264 | 633k | x = x * x; | 265 | 633k | y >>= 1; | 266 | 633k | } | 267 | 126k | return result; | 268 | 126k | } |
Unexecuted instantiation: _ZN2AK7pow_intITkNS_8Concepts13FloatingPointEdTkNS1_16UnsignedIntegralEmEET_S2_T0_ |
269 | | |
270 | | template<FloatingPoint T> |
271 | | constexpr T pow(T x, T y) |
272 | 16.7M | { |
273 | 16.7M | CONSTEXPR_STATE(pow, x, y); |
274 | 16.7M | if (__builtin_isnan(y)) |
275 | 0 | return y; |
276 | 16.7M | if (y == 0) |
277 | 6.90k | return 1; |
278 | 16.7M | if (x == 0) |
279 | 12.5M | return 0; |
280 | 4.26M | if (y == 1) |
281 | 3.10k | return x; |
282 | | |
283 | | // Take an integer fast path as long as the value fits within a 64-bit integer. |
284 | 4.26M | if (y >= static_cast<T>(NumericLimits<i64>::min()) && y < static_cast<T>(NumericLimits<i64>::max())) { |
285 | 4.26M | i64 y_as_int = static_cast<i64>(y); |
286 | 4.26M | if (y == static_cast<T>(y_as_int)) { |
287 | 126k | T result = pow_int(x, static_cast<u64>(fabs<T>(y))); |
288 | 126k | if (y < 0) |
289 | 99.2k | result = static_cast<T>(1.0l) / result; |
290 | 126k | return result; |
291 | 126k | } |
292 | 4.26M | } |
293 | | |
294 | | // FIXME: This formula suffers from error magnification. |
295 | 4.13M | return exp2<T>(y * log2<T>(x)); |
296 | 4.26M | } _ZN2AK3powITkNS_8Concepts13FloatingPointEfEET_S2_S2_ Line | Count | Source | 272 | 16.7M | { | 273 | 16.7M | CONSTEXPR_STATE(pow, x, y); | 274 | 16.7M | if (__builtin_isnan(y)) | 275 | 0 | return y; | 276 | 16.7M | if (y == 0) | 277 | 6.90k | return 1; | 278 | 16.7M | if (x == 0) | 279 | 12.5M | return 0; | 280 | 4.26M | if (y == 1) | 281 | 3.10k | return x; | 282 | | | 283 | | // Take an integer fast path as long as the value fits within a 64-bit integer. | 284 | 4.26M | if (y >= static_cast<T>(NumericLimits<i64>::min()) && y < static_cast<T>(NumericLimits<i64>::max())) { | 285 | 4.26M | i64 y_as_int = static_cast<i64>(y); | 286 | 4.26M | if (y == static_cast<T>(y_as_int)) { | 287 | 126k | T result = pow_int(x, static_cast<u64>(fabs<T>(y))); | 288 | 126k | if (y < 0) | 289 | 99.2k | result = static_cast<T>(1.0l) / result; | 290 | 126k | return result; | 291 | 126k | } | 292 | 4.26M | } | 293 | | | 294 | | // FIXME: This formula suffers from error magnification. | 295 | 4.13M | return exp2<T>(y * log2<T>(x)); | 296 | 4.26M | } |
Unexecuted instantiation: _ZN2AK3powITkNS_8Concepts13FloatingPointEdEET_S2_S2_ Unexecuted instantiation: _ZN2AK3powITkNS_8Concepts13FloatingPointEdEET_S2_S2_ |
297 | | |
298 | | #include <AK/Math/UndefMacros.h> |
299 | | |
300 | | } |