FastLED 3.10.8
Loading...
Searching...
No Matches
u16x16.h
Go to the documentation of this file.
1#pragma once
2
3// Unsigned 16.16 fixed-point arithmetic.
4// All operations are integer-only in the hot path.
5
6#include "fl/stl/stdint.h"
11#include "fl/stl/noexcept.h"
12#include "fl/stl/undef.h" // Undefine abs/min/max macros from Arduino.h
13
15
16namespace fl {
17
18// Unsigned 16.16 fixed-point value type.
19class u16x16 {
20 public:
21 static constexpr int INT_BITS = 16;
22 static constexpr int FRAC_BITS = 16;
23 static constexpr i32 SCALE = static_cast<i32>(1) << FRAC_BITS;
24
25 // ---- Construction ------------------------------------------------------
26
27 constexpr u16x16() FL_NO_EXCEPT = default;
28
29 explicit constexpr u16x16(float f) FL_NO_EXCEPT
30 : mValue(static_cast<u32>(f * (SCALE))) {}
31
32 // Integer constructor — any integer width (portable: AVR 16-bit int, ARM/x86 32-bit).
33 // Compile error if constexpr value exceeds INT_BITS range.
34 template <typename IntT, detail::enable_if_integer_t<IntT> = 0>
37
38 // Auto-promotion from other fixed-point types
39 // Enabled only when both INT_BITS and FRAC_BITS can be promoted (no demotion)
40 template <typename OtherFP>
41 constexpr u16x16(const OtherFP& other,
42 typename fl::enable_if<
43 (OtherFP::INT_BITS <= INT_BITS) &&
44 (OtherFP::FRAC_BITS <= FRAC_BITS) &&
45 (OtherFP::INT_BITS != INT_BITS || OtherFP::FRAC_BITS != FRAC_BITS),
46 int>::type = 0)
47 FL_NO_EXCEPT : mValue(static_cast<u32>(
48 static_cast<u64>(other.raw()) << (FRAC_BITS - OtherFP::FRAC_BITS))) {}
49
50 // Raw constructor for C++11 constexpr from_raw
51 struct RawTag {};
52 constexpr explicit u16x16(u32 raw, RawTag) FL_NO_EXCEPT : mValue(raw) {}
53
55 return u16x16(raw, RawTag());
56 }
57
58 // ---- Access ------------------------------------------------------------
59
60 constexpr u32 raw() const FL_NO_EXCEPT { return mValue; }
61 constexpr u32 to_int() const FL_NO_EXCEPT { return mValue >> FRAC_BITS; }
62 constexpr float to_float() const FL_NO_EXCEPT { return static_cast<float>(mValue) / (SCALE); }
63
64 // ---- Fixed-point arithmetic --------------------------------------------
65
66#if defined(__riscv) && (__riscv_xlen == 32)
67 // RV32: Use mul+mulhu inline asm to avoid expensive 64-bit widening multiply.
68 // Extracts bits [47:16] of the 64-bit product for Q16.16 unsigned fixed-point.
70 u32 lo, hi;
71 asm("mul %0, %2, %3\n\t"
72 "mulhu %1, %2, %3"
73 : "=&r"(lo), "=&r"(hi)
74 : "r"(mValue), "r"(b.mValue));
75 return from_raw((lo >> 16) | (hi << 16));
76 }
77#else
79 return from_raw(static_cast<u32>(
80 (static_cast<u64>(mValue) * b.mValue) >> FRAC_BITS));
81 }
82#endif
83
85#if FL_FIXED_POINT_NARROW_DIVIDE
86 // See `s16x16::operator/`. FastLED#4307 measured this one at 33x the
87 // cost of `s8x8`; the signed twin pays more again for the sign
88 // handling `__aeabi_ldivmod` layers on top.
89 return from_raw(divide64By32(mValue >> (32 - FRAC_BITS),
90 mValue << FRAC_BITS, b.mValue));
91#else
92 return from_raw(static_cast<u32>(
93 (static_cast<u64>(mValue) * (static_cast<u64>(1) << FRAC_BITS)) / b.mValue));
94#endif
95 }
96
98 return from_raw(mValue + b.mValue);
99 }
100
102 return from_raw(mValue - b.mValue);
103 }
104
106 return from_raw(mValue >> shift);
107 }
108
110 return from_raw(mValue << shift);
111 }
112
113 // ---- Scalar multiply (no fixed-point shift) ----------------------------
114
115 constexpr FASTLED_FORCE_INLINE u16x16 operator*(u32 scalar) const FL_NO_EXCEPT {
116 return from_raw(mValue * scalar);
117 }
118
119 friend constexpr u16x16 operator*(u32 scalar, u16x16 fp) FL_NO_EXCEPT {
120 return u16x16::from_raw(scalar * fp.mValue);
121 }
122
123 // ---- Comparisons -------------------------------------------------------
124
125 constexpr bool operator<(u16x16 b) const FL_NO_EXCEPT { return mValue < b.mValue; }
126 constexpr bool operator>(u16x16 b) const FL_NO_EXCEPT { return mValue > b.mValue; }
127 constexpr bool operator<=(u16x16 b) const FL_NO_EXCEPT { return mValue <= b.mValue; }
128 constexpr bool operator>=(u16x16 b) const FL_NO_EXCEPT { return mValue >= b.mValue; }
129 constexpr bool operator==(u16x16 b) const FL_NO_EXCEPT { return mValue == b.mValue; }
130 constexpr bool operator!=(u16x16 b) const FL_NO_EXCEPT { return mValue != b.mValue; }
131
132 // ---- Math ---------------------------------------------------------------
133
135 return from_raw(a.mValue % b.mValue);
136 }
137
139 return from_raw(x.mValue & ~(u32((SCALE) - 1)));
140 }
141
143 return from_raw((x.mValue & ~(u32((SCALE) - 1))) +
144 ((x.mValue & u32((SCALE) - 1)) ? (SCALE) : 0));
145 }
146
148 return from_raw(x.mValue & u32((SCALE) - 1));
149 }
150
152 return a < b ? a : b;
153 }
154
156 return a > b ? a : b;
157 }
158
160 return x < lo ? lo : (x > hi ? hi : x);
161 }
162
163#if defined(__riscv) && (__riscv_xlen == 32)
165#else
167#endif
168 return a + (b - a) * t;
169 }
170
172 return x < edge ? u16x16() : u16x16(1.0f);
173 }
174
176 constexpr u16x16 zero(0.0f);
177 constexpr u16x16 one(1.0f);
178 constexpr u16x16 two(2.0f);
179 constexpr u16x16 three(3.0f);
180 u16x16 t = clamp((x - edge0) / (edge1 - edge0), zero, one);
181 return t * t * (three - two * t);
182 }
183
185 return x.mValue == 0 ? u16x16() : from_raw(static_cast<u32>(
186 fl::isqrt64(static_cast<u64>(x.mValue) << FRAC_BITS)));
187 }
188
190 return sqrt(x).mValue == 0
191 ? u16x16()
192 : from_raw(static_cast<u32>(1) << FRAC_BITS) / sqrt(x);
193 }
194
196 constexpr u16x16 one(1.0f);
197 if (exp.mValue == 0) return one;
198 if (base == one) return one;
199 if (base.mValue == 0) return u16x16();
200 // Snap base values within ~2 ULPs of 1.0 to exactly 1.0 to dodge the
201 // log2(1+t) minimax polynomial's upper-endpoint residual (#2969).
202 constexpr u32 kOneRaw = static_cast<u32>(SCALE);
203 if (base.mValue >= (kOneRaw - 2u) && base.mValue <= kOneRaw) {
204 return one;
205 }
206 return exp2_fp(exp * log2_fp(base));
207 }
208
209 private:
210 u32 mValue = 0;
211
212 // Returns 0-based position of highest set bit, or -1 if v==0.
213 static constexpr FASTLED_FORCE_INLINE int highest_bit(u32 v) FL_NO_EXCEPT {
214 return v == 0 ? -1 : _highest_bit_step(v, 0);
215 }
216
217 static constexpr int _highest_bit_step(u32 v, int r) FL_NO_EXCEPT {
218 return (v & 0xFFFF0000u) ? _highest_bit_step(v >> 16, r + 16)
219 : (v & 0x0000FF00u) ? _highest_bit_step(v >> 8, r + 8)
220 : (v & 0x000000F0u) ? _highest_bit_step(v >> 4, r + 4)
221 : (v & 0x0000000Cu) ? _highest_bit_step(v >> 2, r + 2)
222 : (v & 0x00000002u) ? r + 1
223 : r;
224 }
225
226 // Fixed-point log base 2 for positive values.
227 // Uses 2-term polynomial for log2(1+t), t in [0,1) (simplified for unsigned).
228 // Horner evaluation uses u64 intermediates (24 frac bits) to minimize
229 // rounding error, then converts back to 16 frac bits.
231 u32 val = x.mValue;
232 int msb = highest_bit(val);
233 if (msb < 0) return u16x16(); // log2(0) = undefined, return 0
234
235 u32 int_part = msb - FRAC_BITS;
236 u32 t;
237 if (msb >= FRAC_BITS) {
238 t = (val >> (msb - FRAC_BITS)) - (SCALE);
239 } else {
240 t = (val << (FRAC_BITS - msb)) - (SCALE);
241 }
242
243 // 2-term polynomial coefficients for log2(1+t), t in [0,1).
244 // Stored as u64 with 24 fractional bits.
245 constexpr int IFRAC = 24;
246 constexpr u64 c0 = 24189248ULL; // 1.44179 * 2^24
247 constexpr u64 c1 = 5049984ULL; // 0.30093 * 2^24
248
249 // Extend t from 16 to 24 frac bits.
250 u64 t24 = static_cast<u64>(t) << (IFRAC - FRAC_BITS);
251
252 // Simplified Horner: c0 + c1*t, then multiply by t
253 u64 acc = c0 + ((c1 * t24) >> IFRAC);
254 u64 frac_part = (acc * t24) >> IFRAC;
255
256 // Convert from 24 frac bits back to 16.
257 u32 frac16 = static_cast<u32>(frac_part >> (IFRAC - FRAC_BITS));
258 return from_raw((int_part << FRAC_BITS) + frac16);
259 }
260
261 // Fixed-point 2^x. Uses 4-term minimax polynomial for 2^t, t in [0,1).
262 // Horner evaluation uses u64 intermediates (24 frac bits) to minimize
263 // rounding error, then converts back to 16 frac bits.
265 u16x16 fl_val = floor(x);
266 u16x16 fr = x - fl_val;
267 u32 n = fl_val.mValue >> FRAC_BITS;
268
269 if (n >= INT_BITS) return from_raw(0xFFFFFFFF); // overflow
270
271 u32 int_pow = (SCALE) << n;
272
273 // 4-term minimax coefficients for 2^t - 1, t in [0,1).
274 // Stored as u64 with 24 fractional bits.
275 constexpr int IFRAC = 24;
276 constexpr u64 d0 = 11629376ULL; // 0.69316 * 2^24
277 constexpr u64 d1 = 4038400ULL; // 0.24071 * 2^24
278 constexpr u64 d2 = 895232ULL; // 0.05336 * 2^24
279 constexpr u64 d3 = 214016ULL; // 0.01276 * 2^24
280
281 // Extend fr from 16 to 24 frac bits.
282 u64 fr24 = static_cast<u64>(fr.mValue) << (IFRAC - FRAC_BITS);
283
284 // Horner: 1 + fr * (d0 + fr * (d1 + fr * (d2 + fr * d3)))
285 u64 acc = d3;
286 acc = d2 + ((acc * fr24) >> IFRAC);
287 acc = d1 + ((acc * fr24) >> IFRAC);
288 acc = d0 + ((acc * fr24) >> IFRAC);
289 constexpr u64 one24 = 1ULL << IFRAC;
290 u64 frac_pow24 = one24 + ((acc * fr24) >> IFRAC);
291
292 // Convert from 24 frac bits to 16 frac bits, then scale by int_pow.
293 u32 frac_pow16 = static_cast<u32>(frac_pow24 >> (IFRAC - FRAC_BITS));
294 u64 result = (static_cast<u64>(int_pow) * frac_pow16) >> FRAC_BITS;
295 return from_raw(static_cast<u32>(result));
296 }
297};
298
299} // namespace fl
300
static FASTLED_FORCE_INLINE u16x16 exp2_fp(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:264
static constexpr FASTLED_FORCE_INLINE u16x16 step(u16x16 edge, u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:171
constexpr u16x16(u32 raw, RawTag) FL_NO_EXCEPT
Definition u16x16.h:52
constexpr u32 raw() const FL_NO_EXCEPT
Definition u16x16.h:60
static constexpr int INT_BITS
Definition u16x16.h:21
static constexpr FASTLED_FORCE_INLINE u16x16 max(u16x16 a, u16x16 b) FL_NO_EXCEPT
Definition u16x16.h:155
constexpr u16x16(IntT n) FL_NO_EXCEPT
Definition u16x16.h:35
constexpr bool operator==(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:129
static constexpr FASTLED_FORCE_INLINE u16x16 min(u16x16 a, u16x16 b) FL_NO_EXCEPT
Definition u16x16.h:151
static constexpr FASTLED_FORCE_INLINE u16x16 lerp(u16x16 a, u16x16 b, u16x16 t) FL_NO_EXCEPT
Definition u16x16.h:166
constexpr FASTLED_FORCE_INLINE u16x16 operator<<(int shift) const FL_NO_EXCEPT
Definition u16x16.h:109
constexpr FASTLED_FORCE_INLINE u16x16 operator>>(int shift) const FL_NO_EXCEPT
Definition u16x16.h:105
constexpr FASTLED_FORCE_INLINE u16x16 operator/(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:84
constexpr bool operator<(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:125
static FASTLED_FORCE_INLINE u16x16 pow(u16x16 base, u16x16 exp) FL_NO_EXCEPT
Definition u16x16.h:195
static constexpr FASTLED_FORCE_INLINE u16x16 fract(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:147
u32 mValue
Definition u16x16.h:210
static constexpr FASTLED_FORCE_INLINE u16x16 from_raw(u32 raw) FL_NO_EXCEPT
Definition u16x16.h:54
constexpr FASTLED_FORCE_INLINE u16x16 operator+(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:97
constexpr float to_float() const FL_NO_EXCEPT
Definition u16x16.h:62
static constexpr FASTLED_FORCE_INLINE u16x16 clamp(u16x16 x, u16x16 lo, u16x16 hi) FL_NO_EXCEPT
Definition u16x16.h:159
constexpr FASTLED_FORCE_INLINE u16x16 operator*(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:78
constexpr bool operator<=(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:127
constexpr u32 to_int() const FL_NO_EXCEPT
Definition u16x16.h:61
constexpr FASTLED_FORCE_INLINE u16x16 operator*(u32 scalar) const FL_NO_EXCEPT
Definition u16x16.h:115
static constexpr FASTLED_FORCE_INLINE u16x16 mod(u16x16 a, u16x16 b) FL_NO_EXCEPT
Definition u16x16.h:134
static constexpr int _highest_bit_step(u32 v, int r) FL_NO_EXCEPT
Definition u16x16.h:217
constexpr bool operator!=(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:130
constexpr u16x16(const OtherFP &other, typename fl::enable_if<(OtherFP::INT_BITS<=INT_BITS) &&(OtherFP::FRAC_BITS<=FRAC_BITS) &&(OtherFP::INT_BITS !=INT_BITS||OtherFP::FRAC_BITS !=FRAC_BITS), int >::type=0) FL_NO_EXCEPT
Definition u16x16.h:41
constexpr FASTLED_FORCE_INLINE u16x16 operator-(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:101
static constexpr FASTLED_FORCE_INLINE u16x16 ceil(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:142
constexpr u16x16() FL_NO_EXCEPT=default
friend constexpr u16x16 operator*(u32 scalar, u16x16 fp) FL_NO_EXCEPT
Definition u16x16.h:119
constexpr bool operator>=(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:128
static constexpr FASTLED_FORCE_INLINE u16x16 sqrt(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:184
static FASTLED_FORCE_INLINE u16x16 smoothstep(u16x16 edge0, u16x16 edge1, u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:175
static constexpr FASTLED_FORCE_INLINE u16x16 floor(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:138
static constexpr FASTLED_FORCE_INLINE int highest_bit(u32 v) FL_NO_EXCEPT
Definition u16x16.h:213
constexpr bool operator>(u16x16 b) const FL_NO_EXCEPT
Definition u16x16.h:126
static constexpr FASTLED_FORCE_INLINE u16x16 rsqrt(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:189
static constexpr i32 SCALE
Definition u16x16.h:23
static constexpr int FRAC_BITS
Definition u16x16.h:22
static FASTLED_FORCE_INLINE u16x16 log2_fp(u16x16 x) FL_NO_EXCEPT
Definition u16x16.h:230
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FASTLED_FORCE_INLINE
#define FL_OPTIMIZATION_LEVEL_O3_END
#define constexpr
Declares that it is possible to evaluate a value at compile time, introduced in C++11.
Definition cpp_compat.h:15
FL_FIXED_POINT_DIVIDE_INLINE u32 divide64By32(u32 hi, u32 lo, u32 divisor) FL_NO_EXCEPT
(hi:lo) / divisor, for a quotient that fits 32 bits.
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
FL_OPTIMIZE_FUNCTION constexpr u32 isqrt64(u64 x) FL_NO_EXCEPT
Definition isqrt.h:58
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
enable_if< is_fixed_point< T >::value, T >::type exp(T x) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
fl::u64 u64
Definition stdint.h:220
A 64/32 -> 32 division built from 32-bit divides.