libfxd 0.2.dev
A fixed-point library for C++.
Loading...
Searching...
No Matches
math.hpp
Go to the documentation of this file.
1/*
2 * libfxd - a fixed-point library for C++
3 *
4 * Copyright 2023 Daniel K. O.
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8#ifndef LIBFXD_MATH_HPP
9#define LIBFXD_MATH_HPP
10
11#include <algorithm>
12#include <bit>
13#include <cassert>
14#include <cerrno>
15#include <numeric>
16#include <utility>
17
18#include "casting.hpp"
19#include "compare.hpp"
20#include "concepts.hpp"
21#include "constructors.hpp"
22#include "limits.hpp"
23#include "operators.hpp"
24#include "round-div.hpp"
25#include "round-mul.hpp"
26
27#include "detail/add.hpp"
28#include "detail/shift.hpp"
29
30
31namespace fxd {
32
33
35 template<fixed_point Fxd>
36 constexpr
37 Fxd
38 abs(Fxd f)
39 noexcept
40 {
41 return f.raw_value < 0 ? -f : +f;
42 }
43
44
46 template<fixed_point Fxd>
47 constexpr
48 Fxd
49 fdim(Fxd a,
50 Fxd b)
51 noexcept
52 {
53 if (a > b)
54 return a - b;
55 else
56 return 0;
57 }
58
59
61 template<fixed_point Fxd>
62 constexpr
63 Fxd
64 fma(Fxd a,
65 Fxd b,
66 Fxd c)
67 noexcept
68 {
69 return a * b + c;
70 }
71
72
74 template<fixed_point Fxd>
75 constexpr
76 int
77 ilogb(Fxd x)
78 noexcept
79 {
80 if constexpr (std::numeric_limits<Fxd>::is_signed) {
81 if (x < 0) {
82 if (x == std::numeric_limits<Fxd>::lowest())
83 return Fxd::int_bits - 1;
84 x = -x;
85 }
86 }
87 if (!x)
88 return std::numeric_limits<int>::min();
89
90 using URaw = std::make_unsigned_t<typename Fxd::raw_type>;
91 URaw rx = x.raw_value;
92 return std::bit_width(rx) - 1 - Fxd::frac_bits;
93 }
94
95
96 namespace zero {
98 template<fixed_point Fxd>
99 constexpr
100 Fxd
101 ldexp(Fxd x,
102 int exp)
103 noexcept
104 {
105 if (exp >= 0)
106 return Fxd::from_raw(x.raw_value << exp);
107
108 auto [y, ovf] = detail::overflow::shr_real(x.raw_value, -exp);
109 if (y < 0 && ovf)
110 ++y;
111 return Fxd::from_raw(y);
112 }
113 }
114
115 using zero::ldexp;
116
117
118 namespace down {
120 template<fixed_point Fxd>
121 constexpr
122 Fxd
123 ldexp(Fxd x,
124 int exp)
125 noexcept
126 {
127 return Fxd::from_raw(detail::shl(x.raw_value, exp));
128 }
129 }
130
131
132 namespace up {
134 template<fixed_point Fxd>
135 constexpr
136 Fxd
137 ldexp(Fxd x,
138 int exp)
139 noexcept
140 {
141 if (exp >= 0)
142 return Fxd::from_raw(x.raw_value << exp);
143
144 auto [y, ovf] = detail::overflow::shr_real(x.raw_value, -exp);
145 if (ovf)
146 ++y;
147 return Fxd::from_raw(y);
148 }
149 }
150
151
152
153
155 template<fixed_point Fxd>
156 constexpr
157 Fxd
158 midpoint(Fxd a,
159 Fxd b)
160 noexcept
161 {
162 auto mid = std::midpoint(a.raw_value, b.raw_value);
163 return Fxd::from_raw(mid);
164 }
165
166
168 template<fixed_point Fxd>
169 constexpr
170 Fxd
171 nextafter(Fxd from,
172 Fxd to)
173 noexcept
174 {
175 constexpr Fxd e = std::numeric_limits<Fxd>::epsilon();
176 if (from < to)
177 return from + e;
178 if (from > to)
179 return from - e;
180 return to;
181 }
182
183
184
190 template<unsigned_fixed_point Fxd>
191 requires (Fxd::int_bits > 0 && Fxd::frac_bits >= 0)
192 constexpr
193 Fxd
194 sqrt(Fxd x)
195 noexcept
196 {
197 if (!x)
198 return x;
199
200 // Babylonian/Heron/Newton-Raphson method
201
202 int i = 0;
203 Fxd old_b;
204
205 // invariant: a <= b
206 Fxd a, b = std::max(Fxd{1}, x);
207#if 1
208 // Try to find a better initial guess.
209 int ib = ilogb(x);
210 b = down::ldexp(x, - ib / 2);
211#endif
212
213 do {
214
215 if (++i > Fxd::bits)
216 break;
217
218 assert(b > 0u);
219 a = down::div(x, b);
220 assert(a >= 0u);
221
222 old_b = b;
223 b = midpoint(b, a);
224
225 } while (old_b != b);
226
227 for (;;) {
228 Fxd a_next = a + std::numeric_limits<Fxd>::epsilon();
229 if (up::mul(a_next, a_next) <= x)
230 a = a_next;
231 else
232 break;
233 }
234
235 return a;
236 }
237
238
240 template<signed_fixed_point Fxd>
241 requires (Fxd::int_bits > 1 && Fxd::frac_bits >= 0)
242 constexpr
243 Fxd
244 sqrt(Fxd x)
245 noexcept
246 {
247 if (x < 0) {
248 errno = EDOM;
249 return 0;
250 }
251 auto ux = fxd::fixed_cast<fxd::make_unsigned_t<Fxd>>(x);
252 auto r = sqrt(ux);
253 return fixed_cast<Fxd>(r);
254 }
255
256
257
258 template<unsigned_fixed_point Fxd>
259 requires (Fxd::int_bits > 0 && Fxd::frac_bits >= 0)
260 Fxd
261 sqrt_bin(Fxd x)
262 noexcept
263 {
264 if (!x)
265 return x;
266
267 using Lim = std::numeric_limits<Fxd>;
268
269 const Fxd one = 1;
270
271 // maximum root bit that can be tested without overflow
272 const int top_bit = (Lim::max_bit <= 17
273 ? Lim::max_bit - 1
274 : ilogb(x)) / 2;
275 // minimum root bit that can be tested without rounding
276 const int bot_bit = Lim::min_bit / 2;
277
278 Fxd r = 0;
279 Fxd e = x;
280
281 // compute integral part
282 for (int b = top_bit; b >= 0; --b) {
283 Fxd d = down::ldexp(r, b + 1) + down::ldexp(one, 2 * b);
284 if (d <= e) {
285 e -= d;
286 r += down::ldexp(one, b);
287 if (!e)
288 return r;
289 }
290 }
291
292 // compute fractional part
293 for (int b = -1; b >= bot_bit; --b) {
294 Fxd d = down::ldexp(r, b + 1) + down::ldexp(one, 2 * b);
295 if (d <= e) {
296 e -= d;
297 r += down::ldexp(one, b);
298 if (!e)
299 break;
300 }
301 }
302
303 if (Lim::min_bit >= -32) {
304
305 // Try setting each unset bit to 1, and check the square.
306 for (int b = bot_bit - 1; b >= Lim::min_bit; --b) {
307 Fxd next_r = r + down::ldexp(one, b);
308 if (up::mul(next_r, next_r) <= x)
309 r = next_r;
310 }
311
312 } else {
313
314 // Continue with Newton-Raphson method
315
316 int i = 0;
317
318 Fxd a;
319 Fxd b = r + down::ldexp(one, bot_bit);
320 Fxd old_b;
321
322 do {
323
324 if (++i > Fxd::frac_bits)
325 break;
326
327 a = down::div(x, b);
328
329 old_b = b;
330 b = midpoint(b, a);
331
332 } while (old_b > b);
333
334 for (;;) {
335 Fxd a_next = a + std::numeric_limits<Fxd>::epsilon();
336 if (up::mul(a_next, a_next) <= x)
337 a = a_next;
338 else
339 break;
340 }
341
342 return a;
343 }
344
345 return r;
346 }
347
348
349 template<signed_fixed_point Fxd>
350 requires (Fxd::int_bits > 1 && Fxd::frac_bits >= 0)
351 Fxd
352 sqrt_bin(Fxd x)
353 noexcept
354 {
355 if (x < 0) {
356 errno = EDOM;
357 return 0;
358 }
359 auto ux = fxd::fixed_cast<fxd::make_unsigned_t<Fxd>>(x);
360 auto r = sqrt_bin(ux);
361 return fixed_cast<Fxd>(r);
362 }
363
364
365} // namespace fxd
366
367
368#endif
constexpr Fxd ldexp(Fxd x, int exp) noexcept
Same as std::ldexp().
Definition: math.hpp:123
constexpr Fxd div(Fxd a, Fxd b) noexcept
Divide rounding down.
Definition: round-div.hpp:107
constexpr Fxd mul(Fxd a, Fxd b) noexcept
Multiply rounding up.
Definition: round-mul.hpp:77
constexpr Fxd ldexp(Fxd x, int exp) noexcept
Same as std::ldexp().
Definition: math.hpp:101
This is the namespace where the entire library is defined.
Definition: casting.hpp:19
constexpr Fxd midpoint(Fxd a, Fxd b) noexcept
Same as std::midpoint().
Definition: math.hpp:158
constexpr Fxd sqrt(Fxd x) noexcept
Same as std::sqrt().
Definition: math.hpp:194
constexpr int ilogb(Fxd x) noexcept
Same as std::ilogb().
Definition: math.hpp:77
Fxd sqrt_bin(Fxd x) noexcept
Definition: math.hpp:261
constexpr Fxd nextafter(Fxd from, Fxd to) noexcept
Same as std::nextafter().
Definition: math.hpp:171
constexpr Fxd fdim(Fxd a, Fxd b) noexcept
Same as std::fdim().
Definition: math.hpp:49
constexpr Fxd fma(Fxd a, Fxd b, Fxd c) noexcept
Same as std::fma().
Definition: math.hpp:64