1 /**
2     Bindings to compiler-specific intrinsics wrapped in a nicer interface.
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.intrinsics;
13 import nulib.math.fixed;
14 
15 version (GNU) import gcc.builtins;
16 else version (LDC) import ldc.intrinsics;
17 else {
18     import nmath = nulib.math.floating;
19     import cmath = nulib.c.math;
20 }
21 
22 @safe @nogc nothrow pure:
23 
24 /**
25     Computes the square root of the given value.
26 
27     Params:
28         x = The value
29     
30     Returns:
31         The square root of $(D x).
32 */
33 pragma(inline, true)
34 T sqrt(T)(T x) if (__traits(isFloating, T)) {
35     version(LDC) {
36         return x < 0 ? T.nan : llvm_sqrt(x);
37     }  else version(GNU) {
38         static if (is(T == float))
39             return __builtin_sqrtf(x);
40         else static if (is(T == double))
41             return __builtin_sqrt(x);
42         else static if (is(T == real))
43             return __builtin_sqrtl(x);
44     } else {
45         return cast(T)cmath.sqrt(cast(double)x);
46     }
47 }
48 
49 @("sqrt")
50 unittest {
51     assert(sqrt(9.0) == 3.0);
52     assert(sqrt(10.0) == 3.1622776601683795);
53 }
54 
55 /**
56     Computes sine of the given value.
57 
58     Params:
59         x = The value
60     
61     Returns:
62         The sine of $(D x).
63 */
64 pragma(inline, true)
65 T sin(T)(T x) if (__traits(isFloating, T)) {
66     version(LDC) {
67         return llvm_sin!T(x);
68     } else version(GNU) {
69         static if (is(T == float))
70             return __builtin_sinf(x);
71         else static if (is(T == double))
72             return __builtin_sin(x);
73         else static if (is(T == real))
74             return __builtin_sinl(x);
75     } else {
76         return cast(T)cmath.sin(cast(double)x);
77     }
78 }
79 
80 /**
81     Computes cosine of the given value.
82 
83     Params:
84         x = The value
85     
86     Returns:
87         The cosine of $(D x).
88 */
89 pragma(inline, true)
90 T cos(T)(T x) if (__traits(isFloating, T)) {
91     version(LDC) {
92         return llvm_cos!T(x);
93     } else version(GNU) {
94         static if (is(T == float))
95             return __builtin_cosf(x);
96         else static if (is(T == double))
97             return __builtin_cos(x);
98         else static if (is(T == real))
99             return __builtin_cosl(x);
100     } else {
101         return cast(T)cmath.cos(cast(double)x);
102     }
103 }
104 
105 /**
106     Computes tangent of the given value.
107 
108     Params:
109         x = The value
110     
111     Returns:
112         The tangent of $(D x).
113 */
114 pragma(inline, true)
115 T tan(T)(T x) if (__traits(isFloating, T)) {
116     version(LDC) {
117         return llvm_tan!T(x);
118     } else version(GNU) {
119         static if (is(T == float))
120             return __builtin_tanf(x);
121         else static if (is(T == double))
122             return __builtin_tan(x);
123         else static if (is(T == real))
124             return __builtin_tanl(x);
125     } else {
126         return cast(T)cmath.tan(cast(double)x);
127     }
128 }
129 
130 /**
131     Computes arc-sine of the given value.
132 
133     Params:
134         x = The value
135     
136     Returns:
137         The arc-sine of $(D x).
138 */
139 pragma(inline, true)
140 T asin(T)(T x) if (__traits(isFloating, T)) {
141     version(LDC) {
142         return llvm_asin!T(x);
143     } else version(GNU) {
144         static if (is(T == float))
145             return __builtin_asinf(x);
146         else static if (is(T == double))
147             return __builtin_asin(x);
148         else static if (is(T == real))
149             return __builtin_asinl(x);
150     } else {
151         return cast(T)cmath.asin(cast(double)x);
152     }
153 }
154 
155 /**
156     Computes arc-cosine of the given value.
157 
158     Params:
159         x = The value
160     
161     Returns:
162         The arc-cosine of $(D x).
163 */
164 pragma(inline, true)
165 T acos(T)(T x) if (__traits(isFloating, T)) {
166     version(LDC) {
167         return llvm_acos!T(x);
168     } else version(GNU) {
169         static if (is(T == float))
170             return __builtin_acosf(x);
171         else static if (is(T == double))
172             return __builtin_acos(x);
173         else static if (is(T == real))
174             return __builtin_acosl(x);
175     } else {
176         return cast(T)cmath.acos(cast(double)x);
177     }
178 }
179 
180 /**
181     Computes arc-tangent of the given value.
182 
183     Params:
184         x = The value
185     
186     Returns:
187         The arc-tangent of $(D x).
188 */
189 pragma(inline, true)
190 T atan(T)(T x) if (__traits(isFloating, T)) {
191     version(LDC) {
192         return llvm_atan!T(x);
193     } else version(GNU) {
194         static if (is(T == float))
195             return __builtin_atanf(x);
196         else static if (is(T == double))
197             return __builtin_atan(x);
198         else static if (is(T == real))
199             return __builtin_atanl(x);
200     } else version(DigitalMars) {
201         return nmath.atan(x); 
202     } else {
203         return cast(T)cmath.atan(cast(double)x);
204     }
205 }
206 
207 /**
208     Computes arc-tangent of the given value, using signs to determine quadrant.
209 
210     Params:
211         y = value
212         x = value
213     
214     Returns:
215         The arc-tangent of $(D y / x).
216 */
217 pragma(inline, true)
218 T atan2(T)(T y, T x) if (__traits(isFloating, T)) {
219     version(LDC) {
220         return llvm_atan2!T(y, x);
221     } else version(GNU) {
222         static if (is(T == float))
223             return __builtin_atan2f(y, x);
224         else static if (is(T == double))
225             return __builtin_atan2(y, x);
226         else static if (is(T == real))
227             return __builtin_atan2l(y, x);
228     } else version(DigitalMars) {
229         return nmath.atan2(y, x); 
230     } else {
231         return cast(T)cmath.atan2(cast(double)x, cast(double)y);
232     }
233 }
234 
235 /**
236     Computes hyperbolic sine of the given value.
237 
238     Params:
239         x = The value
240     
241     Returns:
242         The hyperbolic sine of $(D x).
243 */
244 pragma(inline, true)
245 T sinh(T)(T x) if (__traits(isFloating, T)) {
246     version(LDC) {
247         return llvm_sinh!T(x);
248     } else version(GNU) {
249         static if (is(T == float))
250             return __builtin_sinhf(x);
251         else static if (is(T == double))
252             return __builtin_sinh(x);
253         else static if (is(T == real))
254             return __builtin_sinhl(x);
255     } else {
256         return cast(T)cmath.sinh(cast(double)x);
257     }
258 }
259 
260 /**
261     Computes hyperbolic cosine of the given value.
262 
263     Params:
264         x = The value
265     
266     Returns:
267         The hyperbolic cosine of $(D x).
268 */
269 pragma(inline, true)
270 T cosh(T)(T x) if (__traits(isFloating, T)) {
271     version(LDC) {
272         return llvm_cosh!T(x);
273     } else version(GNU) {
274         static if (is(T == float))
275             return __builtin_coshf(x);
276         else static if (is(T == double))
277             return __builtin_cohs(x);
278         else static if (is(T == real))
279             return __builtin_coshl(x);
280     } else {
281         return cast(T)cmath.cosh(cast(double)x);
282     }
283 }
284 
285 /**
286     Computes hyperbolic tangent of the given value.
287 
288     Params:
289         x = The value
290     
291     Returns:
292         The hyperbolic tangent of $(D x).
293 */
294 pragma(inline, true)
295 T tanh(T)(T x) if (__traits(isFloating, T)) {
296     version(LDC) {
297         return llvm_tanh!T(x);
298     } else version(GNU) {
299         static if (is(T == float))
300             return __builtin_tanhf(x);
301         else static if (is(T == double))
302             return __builtin_tanh(x);
303         else static if (is(T == real))
304             return __builtin_tanhl(x);
305     } else {
306         return cast(T)cmath.tanh(cast(double)x);
307     }
308 }
309 
310 /**
311     Computes hyperbolic arc-sine of the given value.
312 
313     Params:
314         x = The value
315     
316     Returns:
317         The hyperbolic arc-sine of $(D x).
318 */
319 pragma(inline, true)
320 T asinh(T)(T x) if (__traits(isFloating, T)) {
321     version(LDC) {
322         return llvm_asin!T(x);
323     } else version(GNU) {
324         static if (is(T == float))
325             return __builtin_asinhf(x);
326         else static if (is(T == double))
327             return __builtin_asinh(x);
328         else static if (is(T == real))
329             return __builtin_asinhl(x);
330     } else {
331         return cast(T)cmath.asinh(cast(double)x);
332     }
333 }
334 
335 /**
336     Computes hyperbolic arc-cosine of the given value.
337 
338     Params:
339         x = The value
340     
341     Returns:
342         The hyperbolic arc-cosine of $(D x).
343 */
344 pragma(inline, true)
345 T acosh(T)(T x) if (__traits(isFloating, T)) {
346     version(LDC) {
347         return llvm_acosh!T(x);
348     } else version(GNU) {
349         static if (is(T == float))
350             return __builtin_acoshf(x);
351         else static if (is(T == double))
352             return __builtin_acosh(x);
353         else static if (is(T == real))
354             return __builtin_acoshl(x);
355     } else {
356         return cast(T)cmath.acosh(cast(double)x);
357     }
358 }
359 
360 /**
361     Computes hyperbolic arc-tangent of the given value.
362 
363     Params:
364         x = The value
365     
366     Returns:
367         The hyperbolic arc-tangent of $(D x).
368 */
369 pragma(inline, true)
370 T atanh(T)(T x) if (__traits(isFloating, T)) {
371     version(LDC) {
372         return llvm_atanh!T(x);
373     } else version(GNU) {
374         static if (is(T == float))
375             return __builtin_atanhf(x);
376         else static if (is(T == double))
377             return __builtin_atanh(x);
378         else static if (is(T == real))
379             return __builtin_atanhl(x);
380     } else {
381         return cast(T)cmath.atanh(cast(double)x);
382     }
383 }
384 
385 /**
386     Computes the nearest integer value lower in magnitude than
387     the given value.
388 
389     Params:
390         x = The value
391     
392     Returns:
393         The nearest integer value lower in magnitude than $(D x).
394 */
395 pragma(inline, true)
396 T trunc(T)(T x) if (__traits(isFloating, T)) {
397     version(LDC) {
398         return llvm_trunc!T(x);
399     } else version(GNU) {
400         static if (is(T == float))
401             return __builtin_truncf(x);
402         else static if (is(T == double))
403             return __builtin_trunc(x);
404         else static if (is(T == real))
405             return __builtin_truncl(x);
406     } else {
407         return cast(T)cmath.trunc(cast(double)x);
408     }
409 }
410 
411 @("trunc")
412 unittest {
413     assert(trunc(1.5) == 1);
414     assert(trunc(1.9999991) == 1);
415     assert(trunc(1.0000001) == 1);
416     assert(trunc(0.9999991) == 0);
417 
418     assert(trunc(cast(float)1.0) == 1);
419     assert(trunc(cast(double)1.0) == 1);
420     assert(trunc(cast(real)1.0) == 1);
421 }
422 
423 /**
424     Computes the nearest integer value, rounded away from 0.
425 
426     Params:
427         value = Input value
428     
429     Returns:
430         The nearest integer value to $(D x).
431 */
432 T round(T)(T value) if (__traits(isFloating, T)) {
433     version(LDC) {
434         return llvm_round!T(value);
435     } else version(GNU) {
436         static if (is(T == float))
437             return __builtin_roundf(value);
438         else static if (is(T == double))
439             return __builtin_round(value);
440         else static if (is(T == real))
441             return __builtin_roundl(value);
442     } else {
443         return cast(T)cmath.round(cast(double)value);
444     }
445 }
446 
447 @("round")
448 unittest {
449     assert(round(0.0) == 0);
450     assert(round(0.25) == 0);
451     assert(round(0.5) == 1);
452     assert(round(0.95) == 1);
453 
454     assert(round(cast(float)1.0) == 1);
455     assert(round(cast(double)1.0) == 1);
456     assert(round(cast(real)1.0) == 1);
457 }
458 
459 /**
460     Computes the nearest integer value lower than the given value.
461 
462     Params:
463         value = Input value
464     
465     Returns:
466         The nearest integer value lower than $(D x).
467 */
468 pragma(inline, true)
469 T floor(T)(T value) if (__traits(isFloating, T)) {
470     version(LDC) {
471         return llvm_floor!T(value);
472     } else version(GNU) {
473         static if (is(T == float))
474             return __builtin_floorf(value);
475         else static if (is(T == double))
476             return __builtin_floor(value);
477         else static if (is(T == real))
478             return __builtin_floorl(value);
479     } else {
480         return cast(T)cmath.floor(cast(double)value);
481     }
482 }
483 
484 @("floor")
485 unittest {
486     assert(floor(0.0) == 0);
487     assert(floor(0.25) == 0);
488     assert(floor(0.5) == 0);
489     assert(floor(0.95) == 0);
490 
491     assert(floor(cast(float)1.0) == 1);
492     assert(floor(cast(double)1.0) == 1);
493     assert(floor(cast(real)1.0) == 1);
494 }
495 
496 /**
497     Computes the nearest integer value lower than the given value.
498 
499     Params:
500         value = Input value
501     
502     Returns:
503         The nearest integer value lower than $(D x).
504 */
505 pragma(inline, true)
506 T ceil(T)(T value) if (__traits(isFloating, T)) {
507     version(LDC) {
508         return llvm_ceil!T(value);
509     } else version(GNU) {
510         static if (is(T == float))
511             return __builtin_ceilf(value);
512         else static if (is(T == double))
513             return __builtin_ceil(value);
514         else static if (is(T == real))
515             return __builtin_ceill(value);
516     } else {
517         return cast(T)cmath.ceil(cast(double)value);
518     }
519 }
520 
521 @("ceil")
522 unittest {
523     assert(ceil(0.0) == 0);
524     assert(ceil(0.25) == 1);
525     assert(ceil(0.5) == 1);
526     assert(ceil(0.95) == 1);
527     
528     assert(ceil(cast(float)1.0) == 1);
529     assert(ceil(cast(double)1.0) == 1);
530     assert(ceil(cast(real)1.0) == 1);
531 }
532 
533 /**
534     Computes the absolute value for the given value.
535 
536     Params:
537         value = the value
538     
539     Returns:
540         The absolute value of $(D value)
541 */
542 pragma(inline, true)
543 T abs(T)(T value) if (__traits(isScalar, T)) {
544     static if (__traits(isFloating, T)) {
545         version(LDC) {
546             return llvm_fabs!T(value);
547         } else version(GNU) {
548             static if (is(T == float))
549                 return __builtin_fabsf(value);
550             else static if (is(T == double))
551                 return __builtin_fabs(value);
552             else static if (is(T == real))
553                 return __builtin_fabsl(value);
554         } else {
555             return cast(T)cmath.fabs(cast(double)value);
556         }
557     } else {
558         return value < 0 ? -value : value;
559     }
560 }
561 
562 @("abs")
563 unittest {
564     foreach(i; 0..100) {
565         assert(abs(cast(float)-i) == cast(float)i);
566     }
567     
568     assert(abs(cast(float)1.0) == 1);
569     assert(abs(cast(double)1.0) == 1);
570     assert(abs(cast(real)1.0) == 1);
571 }
572 
573 /**
574     Rounds the given value to the nearest integer,
575     using the current rounding mode.
576 
577     Params:
578         x = The value
579     
580     Returns:
581         $(D x) rounded to the nearest integer 
582         in respect to rounding mode.
583 */
584 pragma(inline, true)
585 T rint(T)(T x) if (__traits(isFloating, T)) {
586     version(LDC) {
587         return llvm_rint!T(value);
588     } else version(GNU) {
589         static if (is(T == float))
590             return __builtin_rintf(value);
591         else static if (is(T == double))
592             return __builtin_rint(value);
593         else static if (is(T == real))
594             return __builtin_rintl(value);
595     } else {
596         return cast(T)cmath.rint(cast(double)value);
597     }
598 }
599 
600 /**
601     Computes n * 2$(SUPERSCRIPT exp).
602 
603     Params:
604         n =     The value
605         exp =   The exponent
606     
607     Returns:
608         $(D value * pow(exp, 2)).
609 */
610 pragma(inline, true)
611 T ldexp(T)(T n, int exp) if (__traits(isFloating, T)) {
612     version(LDC) {
613         enum RealFormat { ieeeSingle, ieeeDouble, ieeeExtended, ieeeQuadruple }
614 
615              static if (T.mant_dig ==  24) enum realFormat = RealFormat.ieeeSingle;
616         else static if (T.mant_dig ==  53) enum realFormat = RealFormat.ieeeDouble;
617         else static if (T.mant_dig ==  64) enum realFormat = RealFormat.ieeeExtended;
618         else static if (T.mant_dig == 113) enum realFormat = RealFormat.ieeeQuadruple;
619         else static assert(false, "Unsupported format for " ~ T.stringof);
620 
621         version (LittleEndian)
622         {
623             enum MANTISSA_LSB = 0;
624             enum MANTISSA_MSB = 1;
625         }
626         else
627         {
628             enum MANTISSA_LSB = 1;
629             enum MANTISSA_MSB = 0;
630         }
631 
632         static if (realFormat == RealFormat.ieeeExtended)
633         {
634             alias S = int;
635             alias U = ushort;
636             enum sig_mask = U(1) << (U.sizeof * 8 - 1);
637             enum exp_shft = 0;
638             enum man_mask = 0;
639             version (LittleEndian)
640                 enum idx = 4;
641             else
642                 enum idx = 0;
643         }
644         else
645         {
646             static if (realFormat == RealFormat.ieeeQuadruple || realFormat == RealFormat.ieeeDouble && double.sizeof == size_t.sizeof)
647             {
648                 alias S = long;
649                 alias U = ulong;
650             }
651             else
652             {
653                 alias S = int;
654                 alias U = uint;
655             }
656             static if (realFormat == RealFormat.ieeeQuadruple)
657                 alias M = ulong;
658             else
659                 alias M = U;
660             enum sig_mask = U(1) << (U.sizeof * 8 - 1);
661             enum uint exp_shft = T.mant_dig - 1 - (T.sizeof > U.sizeof ? U.sizeof * 8 : 0);
662             enum man_mask = (U(1) << exp_shft) - 1;
663             enum idx = T.sizeof > U.sizeof ? MANTISSA_MSB : 0;
664         }
665         enum exp_mask = (U.max >> (exp_shft + 1)) << exp_shft;
666         enum int exp_msh = exp_mask >> exp_shft;
667         enum intPartMask = man_mask + 1;
668 
669         import core.checkedint : adds;
670         alias _expect = llvm_expect;
671 
672         enum norm_factor = 1 / T.epsilon;
673         T vf = n;
674 
675         auto u = (cast(U*)&vf)[idx];
676         int e = (u & exp_mask) >> exp_shft;
677         if (_expect(e != exp_msh, true))
678         {
679             if (_expect(e == 0, false)) // subnormals input
680             {
681                 bool overflow;
682                 vf *= norm_factor;
683                 u = (cast(U*)&vf)[idx];
684                 e = int((u & exp_mask) >> exp_shft) - (T.mant_dig - 1);
685             }
686             bool overflow;
687             exp = adds(exp, e, overflow);
688             if (_expect(overflow || exp >= exp_msh, false)) // infs
689             {
690                 static if (realFormat == RealFormat.ieeeExtended)
691                 {
692                     return vf * T.infinity;
693                 }
694                 else
695                 {
696                     u &= sig_mask;
697                     u ^= exp_mask;
698                     static if (realFormat == RealFormat.ieeeExtended)
699                     {
700                         version (LittleEndian)
701                             auto mp = cast(ulong*)&vf;
702                         else
703                             auto mp = cast(ulong*)((cast(ushort*)&vf) + 1);
704                         *mp = 0;
705                     }
706                     else
707                     static if (T.sizeof > U.sizeof)
708                     {
709                         (cast(U*)&vf)[MANTISSA_LSB] = 0;
710                     }
711                 }
712             }
713             else
714             if (_expect(exp > 0, true)) // normal
715             {
716                 u = cast(U)((u & ~exp_mask) ^ (cast(typeof(U.init + 0))exp << exp_shft));
717             }
718             else // subnormal output
719             {
720                 exp = 1 - exp;
721                 static if (realFormat != RealFormat.ieeeExtended)
722                 {
723                     auto m = u & man_mask;
724                     if (exp > T.mant_dig)
725                     {
726                         exp = T.mant_dig;
727                         static if (T.sizeof > U.sizeof)
728                             (cast(U*)&vf)[MANTISSA_LSB] = 0;
729                     }
730                 }
731                 u &= sig_mask;
732                 static if (realFormat == RealFormat.ieeeExtended)
733                 {
734                     version (LittleEndian)
735                         auto mp = cast(ulong*)&vf;
736                     else
737                         auto mp = cast(ulong*)((cast(ushort*)&vf) + 1);
738                     if (exp >= ulong.sizeof * 8)
739                         *mp = 0;
740                     else
741                         *mp >>>= exp;
742                 }
743                 else
744                 {
745                     m ^= intPartMask;
746                     static if (T.sizeof > U.sizeof)
747                     {
748                         int exp2 = exp - int(U.sizeof) * 8;
749                         if (exp2 < 0)
750                         {
751                             (cast(U*)&vf)[MANTISSA_LSB] = ((cast(U*)&vf)[MANTISSA_LSB] >> exp) ^ (m << (U.sizeof * 8 - exp));
752                             m >>>= exp;
753                             u ^= cast(U) m;
754                         }
755                         else
756                         {
757                             exp = exp2;
758                             (cast(U*)&vf)[MANTISSA_LSB] = (exp < U.sizeof * 8) ? m >> exp : 0;
759                         }
760                     }
761                     else
762                     {
763                         m >>>= exp;
764                         u ^= cast(U) m;
765                     }
766                 }
767             }
768             (cast(U*)&vf)[idx] = u;
769         }
770         return vf;
771     } else version(GNU) {
772         static if (is(T == float))
773             return __builtin_ldexpf(n, exp);
774         else static if (is(T == double))
775             return __builtin_ldexp(n, exp);
776         else static if (is(T == real))
777             return __builtin_ldexpl(n, exp);
778     } else {
779         return cast(T)cmath.ldexp(cast(double)n, exp);
780     }
781 }
782 
783 
784 
785 //
786 //          EXTRAS
787 //
788 private:
789 
790 version(LDC) {
791 
792     // Trigonometry
793     pragma(LDC_intrinsic, "llvm.tan.f#")
794     T llvm_tan(T)(T val) if (__traits(isFloating, T));
795     
796     pragma(LDC_intrinsic, "llvm.asin.f#")
797     T llvm_asin(T)(T val) if (__traits(isFloating, T));
798     
799     pragma(LDC_intrinsic, "llvm.acos.f#")
800     T llvm_acos(T)(T val) if (__traits(isFloating, T));
801     
802     pragma(LDC_intrinsic, "llvm.atan.f#")
803     T llvm_atan(T)(T val) if (__traits(isFloating, T));
804 
805     pragma(LDC_intrinsic, "llvm.atan2.f#")
806     T llvm_atan2(T)(T y, T x) if (__traits(isFloating, T));
807     
808     
809     pragma(LDC_intrinsic, "llvm.sinh.f#")
810     T llvm_sinh(T)(T val) if (__traits(isFloating, T));
811     
812     pragma(LDC_intrinsic, "llvm.cosh.f#")
813     T llvm_cosh(T)(T val) if (__traits(isFloating, T));
814     
815     pragma(LDC_intrinsic, "llvm.tanh.f#")
816     T llvm_tanh(T)(T val) if (__traits(isFloating, T));
817     
818     
819 
820     pragma(LDC_intrinsic, "llvm.asinh.f#")
821     T llvm_asinh(T)(T val) if (__traits(isFloating, T));
822     
823     pragma(LDC_intrinsic, "llvm.acosh.f#")
824     T llvm_acosh(T)(T val) if (__traits(isFloating, T));
825     
826     pragma(LDC_intrinsic, "llvm.atanh.f#")
827     T llvm_atanh(T)(T val) if (__traits(isFloating, T));
828 }