FastLED 3.9.15
Loading...
Searching...
No Matches
s12x4.h
Go to the documentation of this file.
1#pragma once
2
3// Signed 12.4 fixed-point arithmetic and trigonometry.
4// All operations are integer-only in the hot path.
5
6#include "fl/stl/stdint.h"
7#include "fl/math/sin32.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// Signed 12.4 fixed-point value type.
19class s12x4 {
20 public:
21 static constexpr int INT_BITS = 12;
22 static constexpr int FRAC_BITS = 4;
23 static constexpr i32 SCALE = static_cast<i32>(1) << FRAC_BITS;
24
25 // ---- Construction ------------------------------------------------------
26
27 constexpr s12x4() FL_NOEXCEPT = default;
28
29 explicit constexpr s12x4(float f) FL_NOEXCEPT
30 : mValue(static_cast<i16>(f * (static_cast<i16>(1) << FRAC_BITS))) {}
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 // Raw constructor for C++11 constexpr from_raw
39 struct RawTag {};
40 constexpr explicit s12x4(i16 raw, RawTag) FL_NOEXCEPT : mValue(raw) {}
41
43 return s12x4(raw, RawTag());
44 }
45
46 // ---- Access ------------------------------------------------------------
47
48 constexpr i16 raw() const FL_NOEXCEPT { return mValue; }
49 constexpr i16 to_int() const FL_NOEXCEPT { return mValue >> FRAC_BITS; }
50 constexpr float to_float() const FL_NOEXCEPT { return static_cast<float>(mValue) / (static_cast<i16>(1) << FRAC_BITS); }
51
52 // ---- Fixed-point arithmetic --------------------------------------------
53
55 return from_raw(static_cast<i16>(
56 (static_cast<i32>(mValue) * b.mValue) >> FRAC_BITS));
57 }
58
60 return from_raw(static_cast<i16>(
61 (static_cast<i32>(mValue) * (SCALE)) / b.mValue));
62 }
63
65 return from_raw(static_cast<i16>(
66 static_cast<u16>(mValue) + static_cast<u16>(b.mValue)));
67 }
68
70 return from_raw(static_cast<i16>(
71 static_cast<u16>(mValue) - static_cast<u16>(b.mValue)));
72 }
73
75 return from_raw(static_cast<i16>(
76 static_cast<u16>(0) - static_cast<u16>(mValue)));
77 }
78
79 constexpr FASTLED_FORCE_INLINE s12x4 operator>>(int shift) const FL_NOEXCEPT {
80 return from_raw(mValue >> shift);
81 }
82
83 // ---- Scalar multiply (no fixed-point shift) ----------------------------
84
85 constexpr FASTLED_FORCE_INLINE s12x4 operator*(i16 scalar) const FL_NOEXCEPT {
86 return from_raw(mValue * scalar);
87 }
88
89 friend constexpr s12x4 operator*(i16 scalar, s12x4 fp) FL_NOEXCEPT {
90 return s12x4::from_raw(scalar * fp.mValue);
91 }
92
93 // ---- Comparisons -------------------------------------------------------
94
95 constexpr bool operator<(s12x4 b) const FL_NOEXCEPT { return mValue < b.mValue; }
96 constexpr bool operator>(s12x4 b) const FL_NOEXCEPT { return mValue > b.mValue; }
97 constexpr bool operator<=(s12x4 b) const FL_NOEXCEPT { return mValue <= b.mValue; }
98 constexpr bool operator>=(s12x4 b) const FL_NOEXCEPT { return mValue >= b.mValue; }
99 constexpr bool operator==(s12x4 b) const FL_NOEXCEPT { return mValue == b.mValue; }
100 constexpr bool operator!=(s12x4 b) const FL_NOEXCEPT { return mValue != b.mValue; }
101
102 // ---- Math ---------------------------------------------------------------
103
105 return from_raw(a.mValue % b.mValue);
106 }
107
109 return from_raw(x.mValue & ~(i16((SCALE) - 1)));
110 }
111
113 return from_raw((x.mValue & ~(i16((SCALE) - 1))) +
114 ((x.mValue & i16((SCALE) - 1)) ? (SCALE) : 0));
115 }
116
118 return from_raw(x.mValue & i16((SCALE) - 1));
119 }
120
122 return from_raw(x.mValue < 0 ? -x.mValue : x.mValue);
123 }
124
126 return x.mValue > 0 ? 1 : (x.mValue < 0 ? -1 : 0);
127 }
128
130 return a + (b - a) * t;
131 }
132
134 return x < lo ? lo : (x > hi ? hi : x);
135 }
136
138 return x < edge ? s12x4() : s12x4(1.0f);
139 }
140
142 constexpr s12x4 zero(0.0f);
143 constexpr s12x4 one(1.0f);
144 constexpr s12x4 two(2.0f);
145 constexpr s12x4 three(3.0f);
146 s12x4 t = clamp((x - edge0) / (edge1 - edge0), zero, one);
147 return t * t * (three - two * t);
148 }
149
150 // ---- Inverse Trigonometry (pure fixed-point) ----------------------------
151
153 constexpr s12x4 one(1.0f);
154 constexpr s12x4 pi_over_2(1.5707963f);
155 bool neg = x.mValue < 0;
156 s12x4 ax = abs(x);
158 if (ax <= one) {
159 result = atan_unit(ax);
160 } else {
161 result = pi_over_2 - atan_unit(one / ax);
162 }
163 return neg ? -result : result;
164 }
165
167 constexpr s12x4 pi(3.1415926f);
168 constexpr s12x4 pi_over_2(1.5707963f);
169 if (x.mValue == 0 && y.mValue == 0) return s12x4();
170 if (x.mValue == 0) return y.mValue > 0 ? pi_over_2 : -pi_over_2;
171 if (y.mValue == 0) return x.mValue > 0 ? s12x4() : pi;
172 s12x4 ax = abs(x);
173 s12x4 ay = abs(y);
174 s12x4 a;
175 if (ax >= ay) {
176 a = atan_unit(ay / ax);
177 } else {
178 a = pi_over_2 - atan_unit(ax / ay);
179 }
180 if (x.mValue < 0) a = pi - a;
181 if (y.mValue < 0) a = -a;
182 return a;
183 }
184
186 constexpr s12x4 one(1.0f);
187 return atan2(x, sqrt(one - x * x));
188 }
189
191 constexpr s12x4 one(1.0f);
192 return atan2(sqrt(one - x * x), x);
193 }
194
196 return x.mValue <= 0 ? s12x4() : from_raw(static_cast<i16>(
197 fl::isqrt32(static_cast<u32>(x.mValue) << FRAC_BITS)));
198 }
199
201 return sqrt(x).mValue == 0
202 ? s12x4()
203 : from_raw(SCALE) / sqrt(x);
204 }
205
207 if (base.mValue <= 0) return s12x4();
208 constexpr s12x4 one(1.0f);
209 if (exp.mValue == 0) return one;
210 if (base == one) return one;
211 // Snap base values within ~2 ULPs of 1.0 to exactly 1.0 to dodge the
212 // log2(1+t) minimax polynomial's upper-endpoint residual (#2969).
213 constexpr i16 kOneRaw = static_cast<i16>(SCALE);
214 if (base.mValue >= static_cast<i16>(kOneRaw - 2) &&
215 base.mValue <= kOneRaw) {
216 return one;
217 }
218 return exp2_fp(exp * log2_fp(base));
219 }
220
221 // ---- Member function versions (operate on *this) -----------------------
222
224 return floor(*this);
225 }
226
228 return ceil(*this);
229 }
230
232 return fract(*this);
233 }
234
236 return abs(*this);
237 }
238
239 constexpr FASTLED_FORCE_INLINE int sign() const FL_NOEXCEPT {
240 return sign(*this);
241 }
242
244 return sin(*this);
245 }
246
248 return cos(*this);
249 }
250
252 return atan(*this);
253 }
254
256 return asin(*this);
257 }
258
260 return acos(*this);
261 }
262
264 return sqrt(*this);
265 }
266
268 return rsqrt(*this);
269 }
270
271 // ---- Trigonometry ------------------------------------------------------
272
274 return from_raw(static_cast<i16>(fl::sin32(angle_to_a24(angle)) >> 27));
275 }
276
278 return from_raw(static_cast<i16>(fl::cos32(angle_to_a24(angle)) >> 27));
279 }
280
281 // Combined sin+cos from s12x4 radians. Output in s12x4 [-1, 1].
282 static FASTLED_FORCE_INLINE void sincos(s12x4 angle, s12x4 &out_sin,
283 s12x4 &out_cos) FL_NOEXCEPT {
284 u32 a24 = angle_to_a24(angle);
285 out_sin = from_raw(static_cast<i16>(fl::sin32(a24) >> 27));
286 out_cos = from_raw(static_cast<i16>(fl::cos32(a24) >> 27));
287 }
288
289 private:
290 i16 mValue = 0;
291
292 // Returns 0-based position of highest set bit, or -1 if v==0.
293 static constexpr FASTLED_FORCE_INLINE int highest_bit(u32 v) FL_NOEXCEPT {
294 return v == 0 ? -1 : _highest_bit_step(v, 0);
295 }
296
297 static constexpr int _highest_bit_step(u32 v, int r) FL_NOEXCEPT {
298 return (v & 0xFFFF0000u) ? _highest_bit_step(v >> 16, r + 16)
299 : (v & 0x0000FF00u) ? _highest_bit_step(v >> 8, r + 8)
300 : (v & 0x000000F0u) ? _highest_bit_step(v >> 4, r + 4)
301 : (v & 0x0000000Cu) ? _highest_bit_step(v >> 2, r + 2)
302 : (v & 0x00000002u) ? r + 1
303 : r;
304 }
305
306 // Fixed-point log base 2 for positive values.
307 // Uses 4-term minimax polynomial for log2(1+t), t in [0,1).
308 // Horner evaluation uses i32 intermediates (12 frac bits) to minimize
309 // rounding error, then converts back to 4 frac bits.
311 u32 val = static_cast<u32>(x.mValue);
312 int msb = highest_bit(val);
313 i32 int_part = msb - FRAC_BITS;
314 i32 t;
315 if (msb >= FRAC_BITS) {
316 t = static_cast<i32>(
317 (val >> (msb - FRAC_BITS)) - (SCALE));
318 } else {
319 t = static_cast<i32>(
320 (val << (FRAC_BITS - msb)) - (SCALE));
321 }
322 // 4-term minimax coefficients for log2(1+t), t in [0,1).
323 // Stored as i32 with 12 fractional bits. Max product ~2^21, fits i32 comfortably.
324 constexpr int IFRAC = 12;
325 constexpr i32 c0 = 5907; // 1.44179 * 2^12
326 constexpr i32 c1 = -2864; // -0.69907 * 2^12
327 constexpr i32 c2 = 1489; // 0.36348 * 2^12
328 constexpr i32 c3 = -437; // -0.10660 * 2^12
329 // Extend t from 4 to 12 frac bits.
330 i32 t12 = static_cast<i32>(t) << (IFRAC - FRAC_BITS);
331 // Horner: t * (c0 + t * (c1 + t * (c2 + t * c3)))
332 i32 acc = c3;
333 acc = c2 + ((acc * t12) >> IFRAC);
334 acc = c1 + ((acc * t12) >> IFRAC);
335 acc = c0 + ((acc * t12) >> IFRAC);
336 i32 frac_part = (acc * t12) >> IFRAC;
337 // Convert from 12 frac bits back to 4.
338 i16 frac4 = static_cast<i16>(frac_part >> (IFRAC - FRAC_BITS));
339 return from_raw(static_cast<i16>((static_cast<i32>(static_cast<u32>(int_part) << FRAC_BITS)) + frac4));
340 }
341
342 // Fixed-point 2^x. Uses 4-term minimax polynomial for 2^t, t in [0,1).
343 // Horner evaluation uses i32 intermediates (12 frac bits) to minimize
344 // rounding error, then converts back to 4 frac bits.
346 s12x4 fl_val = floor(x);
347 s12x4 fr = x - fl_val;
348 i32 n = fl_val.mValue >> FRAC_BITS;
349 if (n >= INT_BITS - 1) return from_raw(0x7FFF);
350 if (n < -FRAC_BITS) return s12x4();
351 i32 int_pow;
352 if (n >= 0) {
353 int_pow = static_cast<i32>(SCALE) << n;
354 } else {
355 int_pow = static_cast<i32>(SCALE) >> (-n);
356 }
357 // 4-term minimax coefficients for 2^t - 1, t in [0,1).
358 // Stored as i32 with 12 fractional bits.
359 constexpr int IFRAC = 12;
360 constexpr i32 d0 = 2839; // 0.69316 * 2^12
361 constexpr i32 d1 = 986; // 0.24071 * 2^12
362 constexpr i32 d2 = 219; // 0.05336 * 2^12
363 constexpr i32 d3 = 52; // 0.01276 * 2^12
364 // Extend fr from 4 to 12 frac bits.
365 i32 fr12 = static_cast<i32>(fr.mValue) << (IFRAC - FRAC_BITS);
366 // Horner: 1 + fr * (d0 + fr * (d1 + fr * (d2 + fr * d3)))
367 i32 acc = d3;
368 acc = d2 + ((acc * fr12) >> IFRAC);
369 acc = d1 + ((acc * fr12) >> IFRAC);
370 acc = d0 + ((acc * fr12) >> IFRAC);
371 constexpr i32 one12 = 1 << IFRAC;
372 i32 frac_pow12 = one12 + ((acc * fr12) >> IFRAC);
373 // Convert from 12 frac bits to 4 frac bits, then scale by int_pow.
374 i32 frac_pow4 = frac_pow12 >> (IFRAC - FRAC_BITS);
375 i32 result =
376 (int_pow * frac_pow4) >> FRAC_BITS;
377 return from_raw(static_cast<i16>(result));
378 }
379
380 // Converts s12x4 radians to sin32/cos32 input format.
381 // 256/(2*PI) — converts radians to sin32/cos32 format.
382 static constexpr i32 RAD_TO_24 = 2670177;
384 return static_cast<u32>(
385 (static_cast<i64>(angle.mValue) * RAD_TO_24) >> FRAC_BITS);
386 }
387
388 // Polynomial atan for t in [0, 1]. Returns [0, π/4].
389 // 7th-order minimax: atan(t) ≈ t * (c0 + t² * (c1 + t² * (c2 + t² * c3)))
390 // Coefficients optimized via coordinate descent on s16x16 quantization grid.
392 constexpr s12x4 c0(0.9998779297f);
393 constexpr s12x4 c1(-0.3269348145f);
394 constexpr s12x4 c2(0.1594085693f);
395 constexpr s12x4 c3(-0.0472106934f);
396 s12x4 t2 = t * t;
397 return t * (c0 + t2 * (c1 + t2 * (c2 + t2 * c3)));
398 }
399};
400
401} // namespace fl
402
constexpr FASTLED_FORCE_INLINE s12x4 sqrt() const FL_NOEXCEPT
Definition s12x4.h:263
static constexpr FASTLED_FORCE_INLINE s12x4 rsqrt(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:200
static constexpr FASTLED_FORCE_INLINE s12x4 step(s12x4 edge, s12x4 x) FL_NOEXCEPT
Definition s12x4.h:137
constexpr i16 raw() const FL_NOEXCEPT
Definition s12x4.h:48
constexpr bool operator>(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:96
static constexpr int FRAC_BITS
Definition s12x4.h:22
constexpr bool operator<(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:95
constexpr i16 to_int() const FL_NOEXCEPT
Definition s12x4.h:49
static FASTLED_FORCE_INLINE s12x4 smoothstep(s12x4 edge0, s12x4 edge1, s12x4 x) FL_NOEXCEPT
Definition s12x4.h:141
static constexpr FASTLED_FORCE_INLINE s12x4 ceil(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:112
static FASTLED_FORCE_INLINE s12x4 atan2(s12x4 y, s12x4 x) FL_NOEXCEPT
Definition s12x4.h:166
static FASTLED_FORCE_INLINE void sincos(s12x4 angle, s12x4 &out_sin, s12x4 &out_cos) FL_NOEXCEPT
Definition s12x4.h:282
static constexpr FASTLED_FORCE_INLINE s12x4 floor(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:108
constexpr bool operator>=(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:98
constexpr FASTLED_FORCE_INLINE s12x4 abs() const FL_NOEXCEPT
Definition s12x4.h:235
friend constexpr s12x4 operator*(i16 scalar, s12x4 fp) FL_NOEXCEPT
Definition s12x4.h:89
static constexpr FASTLED_FORCE_INLINE int highest_bit(u32 v) FL_NOEXCEPT
Definition s12x4.h:293
static constexpr int INT_BITS
Definition s12x4.h:21
FASTLED_FORCE_INLINE s12x4 acos() const FL_NOEXCEPT
Definition s12x4.h:259
FASTLED_FORCE_INLINE s12x4 cos() const FL_NOEXCEPT
Definition s12x4.h:247
static FASTLED_FORCE_INLINE s12x4 atan_unit(s12x4 t) FL_NOEXCEPT
Definition s12x4.h:391
static constexpr i32 RAD_TO_24
Definition s12x4.h:382
constexpr bool operator<=(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:97
constexpr FASTLED_FORCE_INLINE s12x4 floor() const FL_NOEXCEPT
Definition s12x4.h:223
constexpr s12x4(i16 raw, RawTag) FL_NOEXCEPT
Definition s12x4.h:40
constexpr FASTLED_FORCE_INLINE int sign() const FL_NOEXCEPT
Definition s12x4.h:239
FASTLED_FORCE_INLINE s12x4 asin() const FL_NOEXCEPT
Definition s12x4.h:255
FASTLED_FORCE_INLINE s12x4 atan() const FL_NOEXCEPT
Definition s12x4.h:251
constexpr float to_float() const FL_NOEXCEPT
Definition s12x4.h:50
constexpr FASTLED_FORCE_INLINE s12x4 operator*(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:54
static constexpr FASTLED_FORCE_INLINE s12x4 sqrt(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:195
static FASTLED_FORCE_INLINE s12x4 pow(s12x4 base, s12x4 exp) FL_NOEXCEPT
Definition s12x4.h:206
constexpr FASTLED_FORCE_INLINE s12x4 ceil() const FL_NOEXCEPT
Definition s12x4.h:227
FASTLED_FORCE_INLINE s12x4 sin() const FL_NOEXCEPT
Definition s12x4.h:243
constexpr FASTLED_FORCE_INLINE s12x4 operator-(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:69
static FASTLED_FORCE_INLINE s12x4 acos(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:190
constexpr s12x4(IntT n) FL_NOEXCEPT
Definition s12x4.h:35
constexpr FASTLED_FORCE_INLINE s12x4 operator>>(int shift) const FL_NOEXCEPT
Definition s12x4.h:79
static constexpr FASTLED_FORCE_INLINE u32 angle_to_a24(s12x4 angle) FL_NOEXCEPT
Definition s12x4.h:383
i16 mValue
Definition s12x4.h:290
static FASTLED_FORCE_INLINE s12x4 atan(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:152
constexpr FASTLED_FORCE_INLINE s12x4 operator-() const FL_NOEXCEPT
Definition s12x4.h:74
static FASTLED_FORCE_INLINE s12x4 cos(s12x4 angle) FL_NOEXCEPT
Definition s12x4.h:277
constexpr bool operator!=(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:100
static FASTLED_FORCE_INLINE s12x4 sin(s12x4 angle) FL_NOEXCEPT
Definition s12x4.h:273
constexpr FASTLED_FORCE_INLINE s12x4 fract() const FL_NOEXCEPT
Definition s12x4.h:231
static FASTLED_FORCE_INLINE s12x4 exp2_fp(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:345
static constexpr FASTLED_FORCE_INLINE s12x4 lerp(s12x4 a, s12x4 b, s12x4 t) FL_NOEXCEPT
Definition s12x4.h:129
static constexpr FASTLED_FORCE_INLINE s12x4 mod(s12x4 a, s12x4 b) FL_NOEXCEPT
Definition s12x4.h:104
constexpr FASTLED_FORCE_INLINE s12x4 operator/(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:59
static FASTLED_FORCE_INLINE s12x4 asin(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:185
static constexpr FASTLED_FORCE_INLINE s12x4 clamp(s12x4 x, s12x4 lo, s12x4 hi) FL_NOEXCEPT
Definition s12x4.h:133
static constexpr FASTLED_FORCE_INLINE int sign(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:125
static constexpr i32 SCALE
Definition s12x4.h:23
static constexpr FASTLED_FORCE_INLINE s12x4 fract(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:117
constexpr s12x4() FL_NOEXCEPT=default
constexpr bool operator==(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:99
static FASTLED_FORCE_INLINE s12x4 log2_fp(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:310
constexpr FASTLED_FORCE_INLINE s12x4 operator+(s12x4 b) const FL_NOEXCEPT
Definition s12x4.h:64
constexpr FASTLED_FORCE_INLINE s12x4 operator*(i16 scalar) const FL_NOEXCEPT
Definition s12x4.h:85
constexpr FASTLED_FORCE_INLINE s12x4 rsqrt() const FL_NOEXCEPT
Definition s12x4.h:267
static constexpr FASTLED_FORCE_INLINE s12x4 abs(s12x4 x) FL_NOEXCEPT
Definition s12x4.h:121
static constexpr int _highest_bit_step(u32 v, int r) FL_NOEXCEPT
Definition s12x4.h:297
static constexpr FASTLED_FORCE_INLINE s12x4 from_raw(i16 raw) FL_NOEXCEPT
Definition s12x4.h:42
#define constexpr
Declares that it is possible to evaluate a value at compile time, introduced in C++11.
Definition cpp_compat.h:15
FASTLED_FORCE_INLINE i32 cos32(u32 angle) FL_NOEXCEPT
Definition sin32.h:81
FASTLED_FORCE_INLINE i32 sin32(u32 angle) FL_NOEXCEPT
Definition sin32.h:59
fl::i64 i64
Definition s16x16x4.h:222
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_NOEXCEPT
FL_OPTIMIZE_FUNCTION constexpr u16 isqrt32(u32 x) FL_NOEXCEPT
Definition isqrt.h:53
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FASTLED_FORCE_INLINE
#define FL_OPTIMIZATION_LEVEL_O3_END
#define FL_NOEXCEPT