1 /**
2     Nulib Fixed Point Math
3 
4     Copyright:
5         Copyright © 2023-2025, Kitsunebi Games
6         Copyright © 2023-2025, Inochi2D Project
7     
8     License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
9     Authors:
10         Luna Nielsen
11 */
12 module nulib.math.fixed;
13 import numem.casting;
14 import numem.core.traits;
15 
16 /**
17     Gets whether the provided value is a instance of $(D Fixed)
18 */
19 enum isFixed(T) = is(T : Fixed!U, U...);
20 
21 /**
22     Fixed-point math type, fractional bits can be specified, but
23     by default is split evenly; eg Fixed!int will be Q16.16
24 
25     Note:
26         When converting a fixed-precision number to a floating point
27         number, they may not be exact bit-for-bit equal; as such an
28         approximate equals operation is recommended for comparing
29         fixed point math with 
30 */
31 struct Fixed(T, size_t FRACT_BITS = (8*(T.sizeof/2))) if (__traits(isIntegral, T) && !__traits(isUnsigned, T)) {
32 public:
33 @nogc nothrow:
34     T data;
35     
36     /**
37         Shorthand for this type
38     */
39     alias Self = typeof(this);
40     
41     /**
42         Shorthand for the backing type
43     */
44     alias ValueT = T;
45     
46     /**
47         How much to shift the data store in-operation.
48     */
49     enum T SHIFT = FRACT_BITS;
50     
51     /**
52         Mask of fractional part
53     */
54     enum T FRACT_MASK = (cast(T)1LU << SHIFT) - 1;
55 
56     /**
57         Division factor for the given precision of float.
58     */
59     enum FRACT_DIV(Y) = cast(Y)(1LU << SHIFT);
60     
61     /**
62         Mask of integer part
63     */
64     enum T INT_MASK = ~FRACT_MASK;
65 
66     /**
67         Max value of the fixed-precision value
68     */
69     enum Self min = Self.fromData(T.min);
70 
71     /**
72         Max value of the fixed-precision value
73     */
74     enum Self max = Self.fromData(T.max);
75 
76     /**
77         Creates a new instance from raw data.
78     */
79     static auto fromData(T data) {
80         Self t;
81         t.data = data;
82         return t;
83     }
84 
85     /**
86         Constructor
87     */
88     this(Y)(Y other) {
89         static if (__traits(isIntegral, Y)) {
90             this.data = cast(T)(cast(T)other << SHIFT);
91         } else static if (__traits(isFloating, Y)) {
92             this.data = cast(T)(other * FRACT_DIV!Y);
93         } else static if (isFixed!Y) {
94 
95             // Fast path for when you're just assigning between
96             // the same type.
97             static if (other.SHIFT == this.SHIFT) {
98                 this.data = cast(T)other.data;
99                 return;
100             }
101 
102 
103             // Shift integer part over so that we can realign it,
104             // then add in the factional part from the other; making sure we cut out
105             // any fractional part that doesn't fit within our own fractional space.
106             T intPart = cast(T)(((other.data & other.INT_MASK) >> other.SHIFT) << SHIFT);
107             T fractPart = cast(T)(other.data & other.FRACT_MASK);
108 
109             // This step aligns the fractional part with the size of the container.
110             static if (other.SHIFT > SHIFT)
111                 fractPart = (fractPart >> (other.SHIFT-SHIFT));
112             else 
113                 fractPart = (fractPart << (SHIFT-other.SHIFT));
114             
115             this.data = (intPart & INT_MASK) | (fractPart & FRACT_MASK);
116         } else static assert(0, "Unsupported construction");
117     }
118 
119     pragma(inline, true)
120     Y opCast(Y)() const {
121         static if (__traits(isIntegral, Y)) {
122             return cast(Y)(data >> SHIFT);
123         } else static if (__traits(isFloating, Y)) {
124             return (cast(Y)data / FRACT_DIV!Y);
125         } else static assert(0, "Unsupported cast.");
126     }
127 
128     pragma(inline, true)
129     size_t toHash() const @safe pure nothrow {
130         return cast(size_t)this.data;
131     }
132 
133     pragma(inline, true)
134     bool opEquals(R)(const R other) const
135     if (is(R : Fixed!T)) {
136         return this.data == other.data;
137     }
138 
139     pragma(inline, true)
140     bool opEquals(R)(const R other) const
141     if (__traits(isScalar, R)) {
142         return this.data == Fixed!T(other).data;
143     }
144 
145     pragma(inline, true)
146     typeof(this) opBinary(string op, R)(const R rhs) const
147     if (isFixed!R) {
148 
149         // Convert mismatched Fixed!T
150         static if (R.SHIFT != SHIFT) {
151             auto other = typeof(this)(rhs);
152         } else static if (!is(typeof(R.data) == T)) {
153             auto other = typeof(this).fromData(cast(T)rhs.data);
154         } else {
155             auto other = rhs;
156         }
157 
158         // Move out to LONG
159         long x = cast(long)data;
160         long y = cast(long)other.data;
161         long result;
162 
163         static if (op == "+") {
164 
165             result = x + y;
166         } else static if (op == "-") {
167 
168             result = x - y;
169         } else static if (op == "*") {
170             
171             result = (x * y) >> SHIFT;
172         } else static if (op == "/") {
173             static if (T.sizeof < 8 && R.sizeof < 8) {
174 
175                 // NOTE:    For 32-bit and below division, 64 bit provides
176                 //          plenty of space to do the operation.
177                 result = (x << SHIFT) / y;
178             } else {
179                 ulong signBit = 1LU << 63;
180                 ulong rem = x & ~signBit;
181                 ulong div = y & ~signBit;
182 
183                 ulong quo = 0;
184                 int shift = SHIFT;
185                 while(rem && shift > 0) {
186                     ulong d = rem / div;
187                     rem %= div;
188                     quo += d << shift;
189                     
190                     rem <<= 1;
191                     --shift;
192                 }
193 
194                 // NOTE:    Division generally takes up more space as such,
195                 //          this is the best way to get a mostly correct
196                 //          result for 64-bit fixed point numbers.
197                 result = (quo >> 1) | ((x & signBit) ^ (y & signBit));
198             }
199         } else static assert(0, "Operation not supported (yet)");
200 
201         return typeof(this).fromData(cast(T)result);
202     }
203 
204     pragma(inline, true)
205     typeof(this) opBinary(string op, R)(const R rhs) const
206     if (__traits(isScalar, R)) {
207         return this.opBinary!(op, Fixed!T)(Fixed!T(rhs));
208     }
209     
210     pragma(inline, true)
211     typeof(this) opOpAssign(string op, R)(const R rhs)
212     if (isFixed!R) {
213         this = this.opBinary!(op, R)(rhs);
214         return this;
215     }
216     
217     pragma(inline, true)
218     typeof(this) opOpAssign(string op, R)(const R rhs)
219     if (__traits(isScalar, R)) {
220         this = typeof(this)(this.opBinary!(op, Fixed!T)(Fixed!T(rhs)));
221         return this;
222     }
223 
224     pragma(inline, true)
225     typeof(this) opAssign(R)(const R rhs)
226     if (!is(Unqual!R == Unqual!(typeof(this)))) {
227         this.__ctor!R(rhs);
228         return this;
229     }
230 }
231 
232 /**
233     Q2.14 fixed-point number (16-bit)
234 */
235 alias fixed2_14 = Fixed!(short, 14);
236 
237 /**
238     Q26.6 fixed-point number (32-bit)
239 */
240 alias fixed26_6 = Fixed!(int, 6);
241 
242 /**
243     Q2.6 fixed-point number (8-bit)
244 */
245 alias fixed2_6 = Fixed!(byte, 6);
246 
247 /**
248     Q8.8 fixed-point number (16-bit)
249 */
250 alias fixed16 = Fixed!short;
251 
252 /**
253     Q16.16 fixed-point number (32-bit)
254 */
255 alias fixed32 = Fixed!int;
256 
257 /**
258     Q32.32 fixed-point number (64-bit)
259 */
260 alias fixed64 = Fixed!long;
261 
262 @("fixed32: int->fixed32")
263 unittest {
264     assert(cast(int)fixed32(16) == 16);
265 }
266 
267 @("fixed32: fixed32->float")
268 unittest {
269     import std.math : isClose;
270     assert(isClose(cast(float)fixed32(32.32f), 32.32f));
271 }
272 
273 @("fixed32: +")
274 unittest {
275     assert(fixed32(31) + 1 == 32);
276     assert(fixed32(1.5) + 1.5 == 3);
277 }
278 
279 @("fixed32: -")
280 unittest {
281     assert(fixed32(32) - 1 == 31);
282     assert(fixed32(1.5) - 0.5 == 1.0);
283 }
284 
285 @("fixed32: *")
286 unittest {
287     assert(fixed32(16)*2 == 32);
288     assert(fixed32(5.5)*2.5 == 5.5*2.5);
289 }
290 
291 @("fixed32: /")
292 unittest {
293     assert(fixed32(64) / 2 == 32);
294 }
295 
296 @("fixed32: ctor fixed16")
297 unittest {
298     assert(fixed32(fixed16(32)) == 32);
299     assert(fixed32(fixed16(32.5)) == 32.5);
300 }
301 
302 @("fixed16: +")
303 unittest {
304     assert(fixed16(31) + 1 == 32);
305 }
306 
307 @("fixed16: -")
308 unittest {
309     assert(fixed16(32) - 1 == 31);
310 }
311 
312 @("fixed16: *")
313 unittest {
314     assert(fixed16(16)*2 == 32);
315     assert(fixed16(5.5)*2.5 == 5.5*2.5);
316 }
317 
318 @("fixed16: /")
319 unittest {
320     assert(fixed16(64) / 2 == 32);
321 }
322 
323 @("fixed16: ctor fixed32")
324 unittest {
325     assert(fixed16(fixed32(32)) == 32);
326     assert(fixed16(fixed32(32.5)) == 32.5);
327 }
328 
329 @("fixed16: ctor fixed2_14")
330 unittest {
331     assert(fixed16(fixed2_14(1.0)) == 1.0);
332     assert(fixed16(fixed2_14(0.5)) == 0.5);
333 }
334 
335 
336 
337 
338 //
339 //          MATH OPERATIONS.
340 //
341 
342 
343 /**
344     Computes the nearest integer value greater than the given value.
345 
346     Params:
347         x = The value
348     
349     Returns:
350         The nearest integer value greater than $(D x).
351 */
352 T ceil(T)(T x) if (isFixed!T) {
353     return T.fromData((x.data & T.INT_MASK) + (1 << T.SHIFT));
354 }
355 
356 /**
357     Computes the nearest integer value lower than the given value.
358 
359     Params:
360         x = The value
361     
362     Returns:
363         The nearest integer value lower than $(D x).
364 */
365 T floor(T)(T x) if (isFixed!T) {
366     return T.fromData(x.data & T.INT_MASK);
367 }
368 
369 /**
370     Computes the nearest integer value lower in magnitude than
371     the given value.
372 
373     Params:
374         x = The value
375     
376     Returns:
377         The nearest integer value lower in magnitude than $(D x).
378 */
379 T trunc(T)(T x) if (isFixed!T) {
380     return T.fromData(x.data & T.INT_MASK);
381 }
382 
383 /**
384     Gets the fractional part of the value.
385 
386     Params:
387         value = The value to get the fractional portion of
388 
389     Returns:
390         The factional part of the given value.
391 */
392 T fract(T)(T value) if (isFixed!T) {
393     return T.fromData(value.data & T.FRACT_MASK);
394 }
395 
396 @("fixed32: rounding")
397 unittest {
398     assert(fixed32(1.5).trunc() == fixed32(1.0));
399     assert(fixed32(1.5).floor() == fixed32(1.0));
400     assert(fixed32(1.5).ceil() == fixed32(2.0));
401     assert(fixed32(1.5).fract() == fixed32(0.5));
402 }