Deluge Firmware 1.3.0
Build date: 2026.07.15
Loading...
Searching...
No Matches
fixedpoint.h
1/*
2 * Copyright © 2014-2023 Synthstrom Audible Limited
3 *
4 * This file is part of The Synthstrom Audible Deluge Firmware.
5 *
6 * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software Foundation,
8 * either version 3 of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with this program.
15 * If not, see <https://www.gnu.org/licenses/>.
16 */
17#pragma once
18
19#include <bit>
20#include <cmath>
21#include <compare>
22#include <cstdint>
23#include <limits>
24#include <type_traits>
25
26// signed 31 fractional bits (e.g. one would be 1<<31 but can't be represented)
27using q31_t = int32_t;
28
29constexpr q31_t ONE_Q31{2147483647};
30constexpr float ONE_Q31f{2147483647.0f};
31constexpr q31_t ONE_Q15{65536};
32constexpr q31_t NEGATIVE_ONE_Q31{-2147483648};
33constexpr q31_t ONE_OVER_SQRT2_Q31{1518500250};
34
35// This converts the range -2^31 to 2^31 to the range 0-2^31
36static inline q31_t toPositive(q31_t a) __attribute__((always_inline, unused));
37static inline q31_t toPositive(q31_t a) {
38 return ((a / 2) + (1073741824));
39}
40
41// this is only defined for 32 bit arm
42#if defined(__arm__)
43// This multiplies two numbers in signed Q31 fixed point as if they were q32, so the return value is half what it should
44// be. Use this when several corrective shifts can be accumulated and then combined
45static inline q31_t multiply_32x32_rshift32(q31_t a, q31_t b) __attribute__((always_inline, unused));
46static inline q31_t multiply_32x32_rshift32(q31_t a, q31_t b) {
47 q31_t out;
48 asm("smmul %0, %1, %2" : "=r"(out) : "r"(a), "r"(b));
49 return out;
50}
51
52// This multiplies two numbers in signed Q31 fixed point and rounds the result
53static inline q31_t multiply_32x32_rshift32_rounded(q31_t a, q31_t b) __attribute__((always_inline, unused));
54static inline q31_t multiply_32x32_rshift32_rounded(q31_t a, q31_t b) {
55 q31_t out;
56 asm("smmulr %0, %1, %2" : "=r"(out) : "r"(a), "r"(b));
57 return out;
58}
59
60// This multiplies two numbers in signed Q31 fixed point, returning the result in q31. This is more useful for readable
61// multiplies
62static inline q31_t q31_mult(q31_t a, q31_t b) __attribute__((always_inline, unused));
63static inline q31_t q31_mult(q31_t a, q31_t b) {
64 q31_t out;
65 asm("smmul %0, %1, %2" : "=r"(out) : "r"(a), "r"(b));
66 return out * 2;
67}
68
69// This multiplies a number in q31 by a number in q32 (e.g. unsigned, 2^32 representing one), returning a scaled value a
70static inline q31_t q31tRescale(q31_t a, uint32_t proportion) __attribute__((always_inline, unused));
71static inline q31_t q31tRescale(q31_t a, uint32_t proportion) {
72 q31_t out;
73 asm("smmul %0, %1, %2" : "=r"(out) : "r"(a), "r"(proportion));
74 return out;
75}
76
77// Multiplies A and B, adds to sum, and returns output
78static inline q31_t multiply_accumulate_32x32_rshift32_rounded(q31_t sum, q31_t a, q31_t b)
79 __attribute__((always_inline, unused));
80static inline q31_t multiply_accumulate_32x32_rshift32_rounded(q31_t sum, q31_t a, q31_t b) {
81 q31_t out;
82 asm("smmlar %0, %1, %2, %3" : "=r"(out) : "r"(a), "r"(b), "r"(sum));
83 return out;
84}
85
86// Multiplies A and B, adds to sum, and returns output
87static inline q31_t multiply_accumulate_32x32_rshift32(q31_t sum, q31_t a, q31_t b)
88 __attribute__((always_inline, unused));
89static inline q31_t multiply_accumulate_32x32_rshift32(q31_t sum, q31_t a, q31_t b) {
90 q31_t out;
91 asm("smmla %0, %1, %2, %3" : "=r"(out) : "r"(a), "r"(b), "r"(sum));
92 return out;
93}
94
95// Multiplies A and B, subtracts from sum, and returns output
96static inline q31_t multiply_subtract_32x32_rshift32_rounded(q31_t sum, q31_t a, q31_t b)
97 __attribute__((always_inline, unused));
98static inline q31_t multiply_subtract_32x32_rshift32_rounded(q31_t sum, q31_t a, q31_t b) {
99 q31_t out;
100 asm("smmlsr %0, %1, %2, %3" : "=r"(out) : "r"(a), "r"(b), "r"(sum));
101 return out;
102}
103
104// computes limit((val >> rshift), 2**bits)
105template <uint8_t bits>
106static inline int32_t signed_saturate(int32_t val) __attribute__((always_inline, unused));
107template <uint8_t bits>
108static inline int32_t signed_saturate(int32_t val) {
109 int32_t out;
110 asm("ssat %0, %1, %2" : "=r"(out) : "I"(bits), "r"(val));
111 return out;
112}
113
114static inline int32_t add_saturate(int32_t a, int32_t b) __attribute__((always_inline, unused));
115static inline int32_t add_saturate(int32_t a, int32_t b) {
116 int32_t out;
117 asm("qadd %0, %1, %2" : "=r"(out) : "r"(a), "r"(b));
118 return out;
119}
120
121static inline int32_t subtract_saturate(int32_t a, int32_t b) __attribute__((always_inline, unused));
122static inline int32_t subtract_saturate(int32_t a, int32_t b) {
123 int32_t out;
124 asm("qsub %0, %1, %2" : "=r"(out) : "r"(a), "r"(b));
125 return out;
126}
127
128inline int32_t clz(uint32_t input) {
129 int32_t out;
130 asm("clz %0, %1" : "=r"(out) : "r"(input));
131 return out;
132}
133
136static inline q31_t q31_from_float(float value) {
137 asm("vcvt.s32.f32 %0, %0, #31" : "+t"(value));
138 return std::bit_cast<q31_t>(value);
139}
140
143static inline float q31_to_float(q31_t value) {
144 asm("vcvt.f32.s32 %0, %0, #31" : "+t"(value));
145 return std::bit_cast<float>(value);
146}
147#else
148
149static inline q31_t multiply_32x32_rshift32(q31_t a, q31_t b) {
150 return (int32_t)(((int64_t)a * b) >> 32);
151}
152
153// This multiplies two numbers in signed Q31 fixed point and rounds the result
154static inline q31_t multiply_32x32_rshift32_rounded(q31_t a, q31_t b) {
155 return (int32_t)(((int64_t)a * b + 0x80000000) >> 32);
156}
157
158// This multiplies two numbers in signed Q31 fixed point, returning the result in q31. Matches the ARM
159// `smmul`-then-`* 2` of the hardware path (smmul = multiply_32x32_rshift32).
160static inline q31_t q31_mult(q31_t a, q31_t b) {
161 return multiply_32x32_rshift32(a, b) * 2;
162}
163
164// This multiplies a number in q31 by a number in q32, returning a scaled value. The ARM path is `smmul`
165// (a signed multiply) even though proportion is unsigned, so match that by treating proportion as signed.
166static inline q31_t q31tRescale(q31_t a, uint32_t proportion) {
167 return multiply_32x32_rshift32(a, (q31_t)proportion);
168}
169
170// Multiplies A and B, adds to sum, and returns output.
171// NB: keep the 64-bit product term separate and add `sum` last. The old form shifted sum<<32 first
172// (~2^63) then added a*b (~2^62), overflowing int64 (UB) for Q31-scale operands — it diverged from the
173// ARM smmla (which wraps in 32-bit) and, at -O2, the UB corrupted neighbouring code. Mathematically the
174// non-overflowing forms below are identical to the originals: floor((sum<<32 ± a*b)/2^32) = sum ± (a*b>>32).
175static inline q31_t multiply_accumulate_32x32_rshift32(q31_t sum, q31_t a, q31_t b) {
176 return (q31_t)(sum + (((int64_t)a * b) >> 32));
177}
178
179// Multiplies A and B, adds to sum, and returns output, rounding
180static inline q31_t multiply_accumulate_32x32_rshift32_rounded(q31_t sum, q31_t a, q31_t b) {
181 return (q31_t)(sum + (((int64_t)a * b + 0x80000000) >> 32));
182}
183
184// Multiplies A and B, subtracts from sum, and returns output
185static inline q31_t multiply_subtract_32x32_rshift32(q31_t sum, q31_t a, q31_t b) {
186 return (q31_t)(sum + ((-((int64_t)a * b)) >> 32));
187}
188
189// Multiplies A and B, subtracts from sum, and returns output, rounding
190static inline q31_t multiply_subtract_32x32_rshift32_rounded(q31_t sum, q31_t a, q31_t b) {
191 return (q31_t)(sum + ((0x80000000LL - (int64_t)a * b) >> 32));
192}
193
194// Matches ARM `ssat %0, %1, #bits`: clamp to the signed range representable in `bits` bits,
195// i.e. [-2^(bits-1), 2^(bits-1)-1]. (The old host stub `std::min(val, 1<<bits)` clamped only the
196// upper bound, to the wrong value, with no lower bound — silently wrong saturation in the DSP.)
197template <uint8_t bits>
198static inline int32_t signed_saturate(int32_t val) {
199 constexpr int32_t hi = static_cast<int32_t>((static_cast<uint32_t>(1) << (bits - 1)) - 1);
200 constexpr int32_t lo = -hi - 1;
201 return val > hi ? hi : (val < lo ? lo : val);
202}
203
204// Matches ARM `qadd`: saturating add, clamped to the signed 32-bit range (the old host stub
205// `a + b` wrapped on overflow — a ramp past INT32_MAX flipped to negative instead of pinning).
206static inline int32_t add_saturate(int32_t a, int32_t b) __attribute__((always_inline, unused));
207static inline int32_t add_saturate(int32_t a, int32_t b) {
208 int64_t r = static_cast<int64_t>(a) + b;
209 return r > INT32_MAX ? INT32_MAX : (r < INT32_MIN ? INT32_MIN : static_cast<int32_t>(r));
210}
211
212// Matches ARM `qsub`: saturating subtract, clamped to the signed 32-bit range.
213static inline int32_t subtract_saturate(int32_t a, int32_t b) __attribute__((always_inline, unused));
214static inline int32_t subtract_saturate(int32_t a, int32_t b) {
215 int64_t r = static_cast<int64_t>(a) - b;
216 return r > INT32_MAX ? INT32_MAX : (r < INT32_MIN ? INT32_MIN : static_cast<int32_t>(r));
217}
218
219inline int32_t clz(uint32_t input) {
220 // ARM `clz` returns 32 for 0; __builtin_clz(0) is UB. Match the hardware.
221 return input ? __builtin_clz(input) : 32;
222}
223
224[[gnu::always_inline]] constexpr q31_t q31_from_float(float value) {
225 // A float is represented as 32 bits:
226 // 1-bit sign, 8-bit exponent, 24-bit mantissa
227
228 auto bits = std::bit_cast<uint32_t>(value);
229
230 // Sign bit being 1 indicates negative value
231 bool negative = bits & 0x80000000;
232
233 // Extract exponent and adjust for bias (IEEE 754)
234 int32_t exponent = static_cast<int32_t>((bits >> 23) & 0xFF) - 127;
235
236 // saturate if |value| >= 1.f (vcvt.s32.f32 #31 saturates to INT32_MIN / INT32_MAX)
237 if (exponent >= 0) {
238 return negative ? std::numeric_limits<q31_t>::min() : std::numeric_limits<q31_t>::max();
239 }
240
241 // |value| < 2^-31 truncates to 0. (Was `mantissa >> -exponent` — for these tiny values -exponent
242 // is >= 32, i.e. a shift >= the operand width, which is UB and at -O2 corrupted neighbouring code.)
243 int32_t shift = -exponent;
244 if (shift >= 32) {
245 return 0;
246 }
247
248 // extract mantissa (truncating toward zero, matching VCVT's rounding mode)
249 uint32_t mantissa = (bits << 8) | 0x80000000;
250 q31_t output_value = static_cast<q31_t>(mantissa >> shift);
251 return (negative) ? -output_value : output_value;
252}
253
255static inline float q31_to_float(q31_t value) {
256 return static_cast<float>(value) / 2147483648.0f;
257}
258#endif
259
265template <std::size_t FractionalBits, bool Rounded = false, bool FastApproximation = true>
267 static_assert(FractionalBits > 0, "FractionalBits must be greater than 0");
268 static_assert(FractionalBits < 32, "FractionalBits must be less than 32");
269
270 using BaseType = int32_t;
271 using IntermediateType = int64_t;
272
274 [[gnu::always_inline]] static int32_t signed_most_significant_word_multiply_add(int32_t a, int32_t b, int32_t c) {
275 if constexpr (Rounded) {
276 return multiply_accumulate_32x32_rshift32_rounded(a, b, c);
277 }
278 else {
279 return multiply_accumulate_32x32_rshift32(a, b, c);
280 }
281 }
282
283 // a * b
284 [[gnu::always_inline]] static int32_t signed_most_significant_word_multiply(int32_t a, int32_t b) {
285 if constexpr (Rounded) {
286 return multiply_32x32_rshift32_rounded(a, b);
287 }
288 else {
289 return multiply_32x32_rshift32(a, b);
290 }
291 }
292
293 static constexpr BaseType one() noexcept {
294 if constexpr (fractional_bits == 31) {
295 return std::numeric_limits<BaseType>::max();
296 }
297 else {
298 return 1 << fractional_bits;
299 }
300 }
301
302public:
303 constexpr static std::size_t fractional_bits = FractionalBits;
304 constexpr static std::size_t integral_bits = 32 - FractionalBits;
305 constexpr static bool rounded = Rounded;
306 constexpr static bool fast_approximation = FastApproximation;
307
308 // --- Portable emulation of the ARM VFP fixed-point conversion instructions ---
309 //
310 // vcvt.{s32.f32,f32.s32,s32.f64,f64.s32} back the float/double conversions on ARM. Off-target
311 // (no VFP) and during constant evaluation we must reproduce them *bit-exactly*, or the host-sim
312 // diverges from the firmware. The hardware behaviour is: scale by exactly 2^fractional_bits,
313 // round toward zero, saturate to the int32 range, and map NaN to 0. Verified lane-for-lane
314 // against qemu-arm in tests/qemu/spec/fixedpoint_vfp_spec.cpp.
315 //
316 // Note the scale is 2^fractional_bits, NOT one() — one() is INT32_MAX (not 2^31) when
317 // fractional_bits == 31, whereas VFP always uses the power of two. 2^fractional_bits is exact
318 // in double for every valid fractional_bits, so the scaling never rounds.
319 static constexpr double vfp_scale() noexcept { return static_cast<double>(uint64_t{1} << fractional_bits); }
320
322 static constexpr BaseType saturate_to_raw(double scaled) noexcept {
323 // INT32_MAX + 1 == 2^31 and INT32_MIN - 1 == -(2^31)-1, both exact in double. +inf/-inf
324 // compare past these bounds and saturate, matching the hardware.
325 if (scaled >= static_cast<double>(std::numeric_limits<BaseType>::max()) + 1.0) {
326 return std::numeric_limits<BaseType>::max();
327 }
328 if (scaled <= static_cast<double>(std::numeric_limits<BaseType>::min()) - 1.0) {
329 return std::numeric_limits<BaseType>::min();
330 }
331 return static_cast<BaseType>(scaled); // double->int truncates toward zero; guaranteed in range here
332 }
333
335 static constexpr BaseType float_to_raw(double value) noexcept {
336 if (value != value) { // NaN -> 0
337 return 0;
338 }
339 return saturate_to_raw(value * vfp_scale());
340 }
341
343 static constexpr float raw_to_float(BaseType raw) noexcept {
344 return static_cast<float>(static_cast<double>(raw) / vfp_scale());
345 }
346
348 static constexpr double raw_to_double(BaseType raw) noexcept { return static_cast<double>(raw) / vfp_scale(); }
349
351 constexpr FixedPoint() = default;
352
355 template <std::size_t OtherFractionalBits>
356 constexpr explicit FixedPoint(FixedPoint<OtherFractionalBits> other) noexcept : value_(other.raw()) {
357 if constexpr (FractionalBits == OtherFractionalBits) {
358 return;
359 }
360 else if constexpr (FractionalBits > OtherFractionalBits) {
361 // saturate
362 constexpr int32_t shift = FractionalBits - OtherFractionalBits;
363 value_ = signed_saturate<32 - shift>(value_);
364 value_ = (value_ << shift) + (value_ % 2);
365 }
366 else if constexpr (rounded) {
367 // round
368 constexpr int32_t shift = OtherFractionalBits - FractionalBits;
369 value_ >>= shift + ((1 << shift) - 1);
370 }
371 else {
372 // truncate
373 value_ >>= (OtherFractionalBits - FractionalBits);
374 }
375 }
376
379 template <std::integral T>
380 constexpr explicit FixedPoint(T value) noexcept : value_(static_cast<BaseType>(value) << fractional_bits) {}
381
384 constexpr explicit FixedPoint(float value) noexcept {
385#if defined(__arm__)
386 // Note: a plain (not `if constexpr`) runtime check — `if constexpr (std::is_constant_evaluated())`
387 // is always true, which would discard the VFP path entirely. On host there is no VFP, so the asm
388 // branch is compiled out and the portable path below is always taken.
389 if (!std::is_constant_evaluated()) {
390 asm("vcvt.s32.f32 %0, %0, %1" : "+t"(value) : "I"(fractional_bits));
391 value_ = std::bit_cast<int32_t>(value); // NOLINT
392 return;
393 }
394#endif
395 value_ = float_to_raw(value);
396 }
397
400 constexpr explicit operator float() const noexcept {
401#if defined(__arm__)
402 if (!std::is_constant_evaluated()) {
403 int32_t output = value_;
404 asm("vcvt.f32.s32 %0, %0, %1" : "+t"(output) : "I"(fractional_bits));
405 return std::bit_cast<float>(output);
406 }
407#endif
408 return raw_to_float(value_);
409 }
410
413 constexpr explicit FixedPoint(double value) noexcept {
414#if defined(__arm__)
415 if (!std::is_constant_evaluated()) {
416 auto output = std::bit_cast<int64_t>(value);
417 // %P selects the 64-bit d-register name (the fixed-point vcvt.*.f64 operate on a d-reg; a
418 // bare %0 prints the single-precision s-name). The instruction is in-place — operands must
419 // be the same register — so use a single read-write (+w) operand rather than tied =w/w.
420 asm("vcvt.s32.f64 %P0, %P0, %1" : "+w"(output) : "I"(fractional_bits));
421 value_ = static_cast<BaseType>(output);
422 return;
423 }
424#endif
425 value_ = float_to_raw(value);
426 }
427
430 explicit operator double() const noexcept {
431#if defined(__arm__)
432 if (!std::is_constant_evaluated()) {
433 auto output = std::bit_cast<double>((int64_t)value_);
434 asm("vcvt.f64.s32 %P0, %P0, %1" : "+w"(output) : "I"(fractional_bits));
435 return output;
436 }
437#endif
438 return raw_to_double(value_);
439 }
440
442 template <std::size_t OutputFractionalBits>
444 return static_cast<FixedPoint<OutputFractionalBits>>(*this);
445 }
446
448 template <std::integral T>
449 explicit operator T() const noexcept {
450 return static_cast<T>(value_ >> fractional_bits);
451 }
452
453 explicit operator bool() const noexcept { return value_ != 0; }
454
456 constexpr FixedPoint operator-() const { return FixedPoint::from_raw(-value_); }
457
459 [[nodiscard]] constexpr BaseType raw() const noexcept { return value_; }
460
462 static constexpr FixedPoint from_raw(BaseType raw) noexcept {
463 FixedPoint result{};
464 result.value_ = raw;
465 return result;
466 }
467
470 constexpr FixedPoint operator+(const FixedPoint& rhs) const { return from_raw(add_saturate(value_, rhs.value_)); }
471
474 constexpr FixedPoint operator+=(const FixedPoint& rhs) {
475 value_ = add_saturate(value_, rhs.value_);
476 return *this;
477 }
478
481 constexpr FixedPoint operator-(const FixedPoint& rhs) const {
482 return from_raw(subtract_saturate(value_, rhs.value_));
483 }
484
487 constexpr FixedPoint operator-=(const FixedPoint& rhs) {
488 value_ = subtract_saturate(value_, rhs.value_);
489 return *this;
490 }
491
494 constexpr FixedPoint operator*(const FixedPoint& rhs) const {
495 if constexpr (fast_approximation && fractional_bits > 16) {
496 // less than 16 would mean no fractional bits remain after right shift by 32
497 constexpr int32_t shift = fractional_bits - ((fractional_bits * 2) - 32);
498 return from_raw(signed_most_significant_word_multiply(value_, rhs.value_) << shift);
499 }
500
501 if constexpr (rounded) {
502 IntermediateType value = (static_cast<IntermediateType>(value_) * rhs.value_) >> (fractional_bits - 1);
503 return from_raw(static_cast<BaseType>((value >> 1) + (value % 2)));
504 }
505
506 IntermediateType value = (static_cast<IntermediateType>(value_) * rhs.value_) >> fractional_bits;
507 return from_raw(static_cast<BaseType>(value));
508 }
509
512 constexpr FixedPoint operator*=(const FixedPoint& rhs) {
513 value_ = this->operator*(rhs).value_;
514 return *this;
515 }
516
519 template <std::size_t OutputFractionalBits = FractionalBits, std::size_t OtherFractionalBits, bool OtherRounded,
520 bool OtherApproximating>
521 requires(OtherRounded == Rounded && OtherApproximating == FastApproximation)
524 if constexpr (fast_approximation) {
525 constexpr int32_t l_shift = OutputFractionalBits - ((FractionalBits + OtherFractionalBits) - 32);
526 static_assert(l_shift < 32 && l_shift > -32);
527 BaseType value = signed_most_significant_word_multiply(value_, rhs.raw());
528 return from_raw(l_shift > 0 ? value << l_shift : value >> -l_shift);
529 }
530
531 constexpr int32_t r_shift = (FractionalBits + OtherFractionalBits) - OutputFractionalBits;
532 if constexpr (rounded) {
533 IntermediateType value = (static_cast<IntermediateType>(value_) * rhs.raw())
534 >> (r_shift - 1); // At this point Q is FractionalBits + OtherFractionalBits
535 return from_raw(static_cast<BaseType>((value / 2) + (value % 2)));
536 }
537
538 IntermediateType value = (static_cast<IntermediateType>(value_) * rhs.raw()) >> r_shift;
539 return from_raw(static_cast<BaseType>(value));
540 }
541
544 template <std::size_t OtherFractionalBits>
546 value_ = this->operator*(rhs).value_;
547 return *this;
548 }
549
552 constexpr FixedPoint operator/(const FixedPoint& rhs) const {
553 if (rhs.value_ == 0) {
554 return from_raw(std::numeric_limits<BaseType>::max());
555 }
556 if constexpr (rounded) {
557 IntermediateType value = (static_cast<IntermediateType>(value_) << (fractional_bits + 1)) / rhs.value_;
558 return from_raw(static_cast<BaseType>((value / 2) + (value % 2)));
559 }
560
561 IntermediateType value = (static_cast<IntermediateType>(value_) << fractional_bits) / rhs.value_;
562 return from_raw(static_cast<BaseType>(value));
563 }
564
567 constexpr FixedPoint operator/=(const FixedPoint& rhs) {
568 value_ = this->operator/(rhs).value_;
569 return *this;
570 }
571
573 template <std::size_t OtherFractionalBitsA, std::size_t OtherFractionalBitsB>
575 const FixedPoint<OtherFractionalBitsB>& b) const {
576 // ensure that the number of fractional bits in the addend is equal to the sum of the number of fractional bits
577 // in multiplicands, minus 32 (due to right shift) before using smmla/smmlar
578 if constexpr (fast_approximation && (OtherFractionalBitsA + OtherFractionalBitsB) - 32 == FractionalBits) {
580 }
581 return *this + static_cast<FixedPoint>(a * b);
582 }
583
585 constexpr FixedPoint MultiplyAdd(const FixedPoint& a, const FixedPoint& b) const {
586 if constexpr (fast_approximation && (((FractionalBits * 2) - 32) == (FractionalBits - 1))) {
587 return from_raw(static_cast<FixedPoint<FractionalBits - 1>>(*this).MultiplyAdd(a, b).raw() << 1);
588 }
589 return *this + (a * b);
590 }
591
593 constexpr bool operator==(const FixedPoint& rhs) const noexcept { return value_ == rhs.value_; }
594
596 constexpr std::strong_ordering operator<=>(const FixedPoint& rhs) const noexcept { return value_ <=> rhs.value_; }
597
599 template <std::size_t OtherFractionalBits>
600 constexpr bool operator==(const FixedPoint<OtherFractionalBits>& rhs) const noexcept {
601 int integral_value = value_ >> fractional_bits;
602 int other_integral_value = rhs.raw() >> OtherFractionalBits;
603 int fractional_value = value_ & ((1 << fractional_bits) - 1); // Mask out the integral part
604 int other_fractional_value = rhs.raw() & ((1 << OtherFractionalBits) - 1); // Mask out the integral parts
605
606 // Shift the fractional part if the number of fractional bits is different
607 if (fractional_bits > OtherFractionalBits) {
608 fractional_value >>= fractional_bits - OtherFractionalBits;
609 }
610 else {
611 other_fractional_value >>= OtherFractionalBits - fractional_bits;
612 }
613
614 return integral_value == other_integral_value && fractional_value == other_fractional_value;
615 }
616
618 template <std::size_t OtherFractionalBits>
619 constexpr std::strong_ordering operator<=>(const FixedPoint<OtherFractionalBits>& rhs) const noexcept {
620 // Compare integral and fractional components separately
621 int integral_value = value_ >> fractional_bits;
622 int other_integral_value = rhs.raw() >> OtherFractionalBits;
623 int fractional_value = value_ & ((1 << fractional_bits) - 1); // Mask out the integral part
624 int other_fractional_value = rhs.raw() & ((1 << OtherFractionalBits) - 1); // Mask out the integral part
625
626 // Shift the fractional part if the number of fractional bits is different
627 if (fractional_bits > OtherFractionalBits) {
628 fractional_value >>= fractional_bits - OtherFractionalBits;
629 }
630 else {
631 other_fractional_value >>= OtherFractionalBits - fractional_bits;
632 }
633
634 if (integral_value < other_integral_value) {
635 return std::strong_ordering::less;
636 }
637 if (integral_value > other_integral_value) {
638 return std::strong_ordering::greater;
639 }
640 if (fractional_value < other_fractional_value) {
641 return std::strong_ordering::less;
642 }
643 if (fractional_value > other_fractional_value) {
644 return std::strong_ordering::greater;
645 }
646 return std::strong_ordering::equal;
647 }
648
650 template <typename T>
651 requires std::integral<T> || std::floating_point<T>
652 constexpr bool operator==(const T& rhs) const noexcept {
653 return value_ == FixedPoint(rhs).value_;
654 }
655
656private:
657 BaseType value_{0};
658};
Fixed point number with a configurable number of fractional bits.
Definition fixedpoint.h:266
constexpr bool operator==(const FixedPoint &rhs) const noexcept
Equality operator.
Definition fixedpoint.h:593
constexpr FixedPoint operator*=(const FixedPoint< OtherFractionalBits > &rhs)
Multiplication operator Multiply two fixed point numbers with different number of fractional bits.
Definition fixedpoint.h:545
constexpr FixedPoint(float value) noexcept
Convert from a float to a fixed point number.
Definition fixedpoint.h:384
constexpr std::strong_ordering operator<=>(const FixedPoint &rhs) const noexcept
Three-way comparison operator.
Definition fixedpoint.h:596
static constexpr BaseType saturate_to_raw(double scaled) noexcept
Round toward zero and saturate a scaled real value to int32, as VFP's f->fixed convert does.
Definition fixedpoint.h:322
static constexpr float raw_to_float(BaseType raw) noexcept
vcvt.f32.s32: fixed point -> float (a single round-to-nearest, as the hardware does).
Definition fixedpoint.h:343
constexpr FixedPoint operator/(const FixedPoint &rhs) const
Division operator Divide two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:552
constexpr FixedPoint(T value) noexcept
Convert an integer to a fixed point number.
Definition fixedpoint.h:380
constexpr FixedPoint< OutputFractionalBits > as() const
Convert to a fixed point number with a different number of fractional bits.
Definition fixedpoint.h:443
constexpr FixedPoint MultiplyAdd(const FixedPoint< OtherFractionalBitsA > &a, const FixedPoint< OtherFractionalBitsB > &b) const
Fused multiply-add operation for fixed point numbers with a different number of fractional bits.
Definition fixedpoint.h:574
constexpr FixedPoint operator/=(const FixedPoint &rhs)
Division operator Divide two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:567
static int32_t signed_most_significant_word_multiply_add(int32_t a, int32_t b, int32_t c)
a + b * c
Definition fixedpoint.h:274
constexpr bool operator==(const FixedPoint< OtherFractionalBits > &rhs) const noexcept
Equality operator for fixed point numbers with different number of fractional bits.
Definition fixedpoint.h:600
constexpr FixedPoint(FixedPoint< OtherFractionalBits > other) noexcept
Construct a fixed point number from another fixed point number.
Definition fixedpoint.h:356
constexpr FixedPoint operator+(const FixedPoint &rhs) const
Addition operator Add two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:470
static constexpr BaseType float_to_raw(double value) noexcept
vcvt.s32.f32 / vcvt.s32.f64: floating point -> fixed point. (float promotes to double exactly....
Definition fixedpoint.h:335
constexpr FixedPoint operator-() const
Negation operator.
Definition fixedpoint.h:456
constexpr FixedPoint operator*(const FixedPoint &rhs) const
Multiplication operator Multiply two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:494
constexpr BaseType raw() const noexcept
Get the internal value.
Definition fixedpoint.h:459
constexpr std::strong_ordering operator<=>(const FixedPoint< OtherFractionalBits > &rhs) const noexcept
Three-way comparison operator for fixed point numbers with different number of fractional bits.
Definition fixedpoint.h:619
static constexpr FixedPoint from_raw(BaseType raw) noexcept
Construct from a raw value.
Definition fixedpoint.h:462
constexpr FixedPoint operator-(const FixedPoint &rhs) const
Subtraction operator Subtract two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:481
constexpr FixedPoint operator*=(const FixedPoint &rhs)
Multiplication operator Multiply two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:512
constexpr FixedPoint(double value) noexcept
Convert from a double to a fixed point number.
Definition fixedpoint.h:413
constexpr FixedPoint operator+=(const FixedPoint &rhs)
Addition operator Add two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:474
constexpr FixedPoint MultiplyAdd(const FixedPoint &a, const FixedPoint &b) const
Fused multiply-add operation for fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:585
constexpr bool operator==(const T &rhs) const noexcept
Equality operator for integers and floating point numbers.
Definition fixedpoint.h:652
constexpr FixedPoint operator-=(const FixedPoint &rhs)
Subtraction operator Subtract two fixed point numbers with the same number of fractional bits.
Definition fixedpoint.h:487
static constexpr double raw_to_double(BaseType raw) noexcept
vcvt.f64.s32: fixed point -> double (exact).
Definition fixedpoint.h:348
constexpr FixedPoint()=default
Default constructor.