The OpenD Programming Language

1 /++
2 Common floating point math functions.
3 
4 This module has generic LLVM-oriented API compatible with all D compilers.
5 
6 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
7 Authors:   Ilia Ki, Phobos Team
8 +/
9 module mir.math.common;
10 
11 import mir.internal.utility: isComplex, isComplexOf, isFloatingPoint;
12 
13 version(LDC)
14 {
15     static import ldc.attributes;
16 
17     private alias AliasSeq(T...) = T;
18 
19     /++
20     Functions attribute, an alias for `AliasSeq!(llvmFastMathFlag("contract"));`.
21         
22     $(UL
23     $(LI 1. Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). )
24     )
25 
26     Note: Can be used with all compilers.
27     +/
28     alias fmamath = AliasSeq!(ldc.attributes.llvmFastMathFlag("contract"));
29 
30     /++
31     Functions attribute, an alias for `AliasSeq!(llvmFastMathFlag("fast"))`.
32     
33     It is similar to $(LREF fastmath), but does not allow unsafe-fp-math.
34     This flag does NOT force LDC to use the reciprocal of an argument rather than perform division.
35 
36     This flag is default for string lambdas.
37 
38     Note: Can be used with all compilers.
39     +/
40     alias optmath = AliasSeq!(ldc.attributes.llvmFastMathFlag("fast"));
41 
42     /++
43     Functions attribute, an alias for `ldc.attributes.fastmath` .
44     
45     $(UL
46 
47     $(LI 1. Enable optimizations that make unsafe assumptions about IEEE math (e.g. that addition is associative) or may not work for all input ranges.
48     These optimizations allow the code generator to make use of some instructions which would otherwise not be usable (such as fsin on X86). )
49 
50     $(LI 2. Allow optimizations to assume the arguments and result are not NaN.
51         Such optimizations are required to retain defined behavior over NaNs,
52         but the value of the result is undefined. )
53 
54     $(LI 3. Allow optimizations to assume the arguments and result are not +$(BACKTICK)-inf.
55         Such optimizations are required to retain defined behavior over +$(BACKTICK)-Inf,
56         but the value of the result is undefined. )
57 
58     $(LI 4. Allow optimizations to treat the sign of a zero argument or result as insignificant. )
59 
60     $(LI 5. Allow optimizations to use the reciprocal of an argument rather than perform division. )
61 
62     $(LI 6. Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). )
63 
64     $(LI 7. Allow algebraically equivalent transformations that may dramatically change results in floating point (e.g. reassociate). )
65     )
66     
67     Note: Can be used with all compilers.
68     +/
69     alias fastmath = ldc.attributes.fastmath;
70 }
71 else
72 enum
73 {
74     /++
75     Functions attribute, an alias for `AliasSeq!(llvmFastMathFlag("contract"));`.
76 
77     $(UL
78     $(LI Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). )
79     )
80 
81     Note: Can be used with all compilers.
82     +/
83     fmamath,
84 
85     /++
86     Functions attribute, an alias for `AliasSeq!(llvmAttr("unsafe-fp-math", "false"), llvmFastMathFlag("fast"))`.
87 
88     It is similar to $(LREF fastmath), but does not allow unsafe-fp-math.
89     This flag does NOT force LDC to use the reciprocal of an argument rather than perform division.
90 
91     This flag is default for string lambdas.
92 
93     Note: Can be used with all compilers.
94     +/
95     optmath,
96 
97     /++
98     Functions attribute, an alias for `ldc.attributes.fastmath = AliasSeq!(llvmAttr("unsafe-fp-math", "true"), llvmFastMathFlag("fast"))` .
99 
100     $(UL
101 
102     $(LI Enable optimizations that make unsafe assumptions about IEEE math (e.g. that addition is associative) or may not work for all input ranges.
103     These optimizations allow the code generator to make use of some instructions which would otherwise not be usable (such as fsin on X86). )
104 
105     $(LI Allow optimizations to assume the arguments and result are not NaN.
106         Such optimizations are required to retain defined behavior over NaNs,
107         but the value of the result is undefined. )
108 
109     $(LI Allow optimizations to assume the arguments and result are not +$(BACKTICK)-inf.
110         Such optimizations are required to retain defined behavior over +$(BACKTICK)-Inf,
111         but the value of the result is undefined. )
112 
113     $(LI Allow optimizations to treat the sign of a zero argument or result as insignificant. )
114 
115     $(LI Allow optimizations to use the reciprocal of an argument rather than perform division. )
116 
117     $(LI Allow floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-and-add). )
118 
119     $(LI Allow algebraically equivalent transformations that may dramatically change results in floating point (e.g. reassociate). )
120     )
121 
122     Note: Can be used with all compilers.
123     +/
124     fastmath
125 }
126 
127 version(LDC)
128 {
129     import ldc.intrinsics: LLVM_version;
130     nothrow @nogc pure @safe:
131 
132     pragma(LDC_intrinsic, "llvm.sqrt.f#")
133     ///
134     T sqrt(T)(in T val) if (isFloatingPoint!T);
135 
136     pragma(LDC_intrinsic, "llvm.sin.f#")
137     ///
138     T sin(T)(in T val) if (isFloatingPoint!T);
139 
140     pragma(LDC_intrinsic, "llvm.cos.f#")
141     ///
142     T cos(T)(in T val) if (isFloatingPoint!T);
143 
144     static if (LLVM_version >= 1300)
145         pragma(LDC_intrinsic, "llvm.powi.f#.i32")
146         ///
147         T powi(T)(in T val, int power) if (isFloatingPoint!T);
148     else 
149         pragma(LDC_intrinsic, "llvm.powi.f#")
150         ///
151         T powi(T)(in T val, int power) if (isFloatingPoint!T);
152 
153     version(mir_core_test)
154     unittest
155     {
156         assert(powi(3.0, int(2)) == 9);
157         float f = 3;
158         assert(powi(f, int(2)) == 9);
159     }
160 
161     pragma(LDC_intrinsic, "llvm.pow.f#")
162     ///
163     T pow(T)(in T val, in T power) if (isFloatingPoint!T);
164 
165     pragma(LDC_intrinsic, "llvm.exp.f#")
166     ///
167     T exp(T)(in T val) if (isFloatingPoint!T);
168 
169     pragma(LDC_intrinsic, "llvm.log.f#")
170     ///
171     T log(T)(in T val) if (isFloatingPoint!T);
172 
173     pragma(LDC_intrinsic, "llvm.fma.f#")
174     ///
175     T fma(T)(T vala, T valb, T valc) if (isFloatingPoint!T);
176 
177     pragma(LDC_intrinsic, "llvm.fabs.f#")
178     ///
179     T fabs(T)(in T val) if (isFloatingPoint!T);
180 
181     pragma(LDC_intrinsic, "llvm.floor.f#")
182     ///
183     T floor(T)(in T val) if (isFloatingPoint!T);
184 
185     pragma(LDC_intrinsic, "llvm.exp2.f#")
186     ///
187     T exp2(T)(in T val) if (isFloatingPoint!T);
188 
189     pragma(LDC_intrinsic, "llvm.log10.f#")
190     ///
191     T log10(T)(in T val) if (isFloatingPoint!T);
192 
193     pragma(LDC_intrinsic, "llvm.log2.f#")
194     ///
195     T log2(T)(in T val) if (isFloatingPoint!T);
196 
197     pragma(LDC_intrinsic, "llvm.ceil.f#")
198     ///
199     T ceil(T)(in T val) if (isFloatingPoint!T);
200 
201     pragma(LDC_intrinsic, "llvm.trunc.f#")
202     ///
203     T trunc(T)(in T val) if (isFloatingPoint!T);
204 
205     pragma(LDC_intrinsic, "llvm.rint.f#")
206     ///
207     T rint(T)(in T val) if (isFloatingPoint!T);
208 
209     pragma(LDC_intrinsic, "llvm.nearbyint.f#")
210     ///
211     T nearbyint(T)(in T val) if (isFloatingPoint!T);
212 
213     pragma(LDC_intrinsic, "llvm.copysign.f#")
214     ///
215     T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T);
216 
217     pragma(LDC_intrinsic, "llvm.round.f#")
218     ///
219     T round(T)(in T val) if (isFloatingPoint!T);
220 
221     pragma(LDC_intrinsic, "llvm.fmuladd.f#")
222     ///
223     T fmuladd(T)(in T vala, in T valb, in T valc) if (isFloatingPoint!T);
224 
225     pragma(LDC_intrinsic, "llvm.minnum.f#")
226     ///
227     T fmin(T)(in T vala, in T valb) if (isFloatingPoint!T);
228 
229     pragma(LDC_intrinsic, "llvm.maxnum.f#")
230     ///
231     T fmax(T)(in T vala, in T valb) if (isFloatingPoint!T);
232 }
233 else version(GNU)
234 {
235     static import gcc.builtins;
236 
237     // Calls GCC builtin for either float (suffix "f"), double (no suffix), or real (suffix "l").
238     private enum mixinGCCBuiltin(string fun) =
239     `static if (T.mant_dig == float.mant_dig) return gcc.builtins.__builtin_`~fun~`f(x);`~
240     ` else static if (T.mant_dig == double.mant_dig) return gcc.builtins.__builtin_`~fun~`(x);`~
241     ` else static if (T.mant_dig == real.mant_dig) return gcc.builtins.__builtin_`~fun~`l(x);`~
242     ` else static assert(0);`;
243 
244     // As above but for two-argument function.
245     private enum mixinGCCBuiltin2(string fun) =
246     `static if (T.mant_dig == float.mant_dig) return gcc.builtins.__builtin_`~fun~`f(x, y);`~
247     ` else static if (T.mant_dig == double.mant_dig) return gcc.builtins.__builtin_`~fun~`(x, y);`~
248     ` else static if (T.mant_dig == real.mant_dig) return gcc.builtins.__builtin_`~fun~`l(x, y);`~
249     ` else static assert(0);`;
250 
251     ///
252     T sqrt(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`sqrt`); }
253     ///
254     T sin(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`sin`); }
255     ///
256     T cos(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`cos`); }
257     ///
258     T pow(T)(in T x, in T power) if (isFloatingPoint!T) { alias y = power; mixin(mixinGCCBuiltin2!`pow`); }
259     ///
260     T powi(T)(in T x, int power) if (isFloatingPoint!T) { alias y = power; mixin(mixinGCCBuiltin2!`powi`); }
261     ///
262     T exp(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`exp`); }
263     ///
264     T log(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`log`); }
265     ///
266     T fabs(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`fabs`); }
267     ///
268     T floor(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`floor`); }
269     ///
270     T exp2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`exp2`); }
271     ///
272     T log10(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`log10`); }
273     ///
274     T log2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`log2`); }
275     ///
276     T ceil(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`ceil`); }
277     ///
278     T trunc(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`trunc`); }
279     ///
280     T rint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`rint`); }
281     ///
282     T nearbyint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`nearbyint`); }
283     ///
284     T copysign(T)(in T x, in T sgn) if (isFloatingPoint!T) { alias y = sgn; mixin(mixinGCCBuiltin2!`copysign`); }
285     ///
286     T round(T)(in T x) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin!`round`); }
287     ///
288     T fmuladd(T)(in T a, in T b, in T c) if (isFloatingPoint!T)
289     {
290         static if (T.mant_dig == float.mant_dig)
291             return gcc.builtins.__builtin_fmaf(a, b, c);
292         else static if (T.mant_dig == double.mant_dig)
293             return gcc.builtins.__builtin_fma(a, b, c);
294         else static if (T.mant_dig == real.mant_dig)
295             return gcc.builtins.__builtin_fmal(a, b, c);
296         else
297             static assert(0);
298     }
299     version(mir_core_test)
300     unittest { assert(fmuladd!double(2, 3, 4) == 2 * 3 + 4); }
301     ///
302     T fmin(T)(in T x, in T y) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin2!`fmin`); }
303     ///
304     T fmax(T)(in T x, in T y) if (isFloatingPoint!T) { mixin(mixinGCCBuiltin2!`fmax`); }
305 }
306 else static if (__VERSION__ >= 2082) // DMD 2.082 onward.
307 {
308     static import std.math;
309     static import core.stdc.math;
310 
311     // Calls either std.math or cmath function for either float (suffix "f")
312     // or double (no suffix). std.math will always be used during CTFE or for
313     // arguments with greater than double precision or if the cmath function
314     // is impure.
315     private enum mixinCMath(string fun) =
316         `pragma(inline, true);
317         static if (!is(typeof(std.math.`~fun~`(0.5f)) == float)
318             && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f))))
319         if (!__ctfe)
320         {
321             static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x);
322             else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x);
323         }
324         return std.math.`~fun~`(x);`;
325 
326     // As above but for two-argument function (both arguments must be floating point).
327     private enum mixinCMath2(string fun) =
328         `pragma(inline, true);
329         static if (!is(typeof(std.math.`~fun~`(0.5f, 0.5f)) == float)
330             && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f, 0.5f))))
331         if (!__ctfe)
332         {
333             static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x, y);
334             else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x, y);
335         }
336         return std.math.`~fun~`(x, y);`;
337 
338     // Some std.math functions have appropriate return types (float,
339     // double, real) without need for a wrapper. We can alias them
340     // directly but we leave the templates afterwards for documentation
341     // purposes and so explicit template instantiation still works.
342     // The aliases will always match before the templates.
343     // Note that you cannot put any "static if" around the aliases or
344     // compilation will fail due to conflict with the templates!
345     alias sqrt = std.math.sqrt;
346     alias sin = std.math.sin;
347     alias cos = std.math.cos;
348     alias exp = std.math.exp;
349     //alias fabs = std.math.fabs;
350     alias floor = std.math.floor;
351     alias exp2 = std.math.exp2;
352     alias ceil = std.math.ceil;
353     alias rint = std.math.rint;
354 
355     ///
356     T sqrt(T)(in T x) if (isFloatingPoint!T) { return std.math.sqrt(x); }
357     ///
358     T sin(T)(in T x) if (isFloatingPoint!T) { return std.math.sin(x); }
359     ///
360     T cos(T)(in T x) if (isFloatingPoint!T) { return std.math.cos(x); }
361     ///
362     T pow(T)(in T x, in T power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); }
363     ///
364     T powi(T)(in T x, int power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); }
365     ///
366     T exp(T)(in T x) if (isFloatingPoint!T) { return std.math.exp(x); }
367     ///
368     T log(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log`); }
369     ///
370     T fabs(T)(in T x) if (isFloatingPoint!T) { return std.math.fabs(x); }
371     ///
372     T floor(T)(in T x) if (isFloatingPoint!T) { return std.math.floor(x); }
373     ///
374     T exp2(T)(in T x) if (isFloatingPoint!T) { return std.math.exp2(x); }
375     ///
376     T log10(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log10`); }
377     ///
378     T log2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log2`); }
379     ///
380     T ceil(T)(in T x) if (isFloatingPoint!T) { return std.math.ceil(x); }
381     ///
382     T trunc(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`trunc`); }
383     ///
384     T rint(T)(in T x) if (isFloatingPoint!T) { return std.math.rint(x); }
385     ///
386     T nearbyint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`nearbyint`); }
387     ///
388     T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T)
389     {
390         alias x = mag;
391         alias y = sgn;
392         mixin(mixinCMath2!`copysign`);
393     }
394     ///
395     T round(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`round`); }
396     ///
397     T fmuladd(T)(in T a, in T b, in T c) if (isFloatingPoint!T) { return a * b + c; }
398     version(mir_core_test)
399     unittest { assert(fmuladd!double(2, 3, 4) == 2 * 3 + 4); }
400     ///
401     T fmin(T)(in T x, in T y) if (isFloatingPoint!T)
402     {
403         version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798
404         {
405             version (CRuntime_Microsoft)
406                 mixin(mixinCMath2!`fmin`);
407             else
408                 return std.math.fmin(x, y);
409         }
410         else
411             mixin(mixinCMath2!`fmin`);
412     }
413     ///
414     T fmax(T)(in T x, in T y) if (isFloatingPoint!T)
415     {
416         version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798
417         {
418             version (CRuntime_Microsoft)
419                 mixin(mixinCMath2!`fmax`);
420             else
421                 return std.math.fmax(x, y);
422         }
423         else
424             mixin(mixinCMath2!`fmax`);
425     }
426 
427     version (mir_core_test) @nogc nothrow pure @safe unittest
428     {
429         // Check the aliases are correct.
430         static assert(is(typeof(sqrt(1.0f)) == float));
431         static assert(is(typeof(sin(1.0f)) == float));
432         static assert(is(typeof(cos(1.0f)) == float));
433         static assert(is(typeof(exp(1.0f)) == float));
434         static assert(is(typeof(fabs(1.0f)) == float));
435         static assert(is(typeof(floor(1.0f)) == float));
436         static assert(is(typeof(exp2(1.0f)) == float));
437         static assert(is(typeof(ceil(1.0f)) == float));
438         static assert(is(typeof(rint(1.0f)) == float));
439 
440         auto x = sqrt!float(2.0f); // Explicit template instantiation still works.
441         auto fp = &sqrt!float; // Can still take function address.
442 
443         // Test for DMD linker problem with fmin on Windows.
444         static assert(is(typeof(fmin!float(1.0f, 1.0f))));
445         static assert(is(typeof(fmax!float(1.0f, 1.0f))));
446     }
447 }
448 else // DMD version prior to 2.082
449 {
450     static import std.math;
451     static import core.stdc.math;
452 
453     // Calls either std.math or cmath function for either float (suffix "f")
454     // or double (no suffix). std.math will always be used during CTFE or for
455     // arguments with greater than double precision or if the cmath function
456     // is impure.
457     private enum mixinCMath(string fun) =
458         `pragma(inline, true);
459         static if (!is(typeof(std.math.`~fun~`(0.5f)) == float)
460             && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f))))
461         if (!__ctfe)
462         {
463             static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x);
464             else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x);
465         }
466         return std.math.`~fun~`(x);`;
467 
468     // As above but for two-argument function (both arguments must be floating point).
469     private enum mixinCMath2(string fun) =
470         `pragma(inline, true);
471         static if (!is(typeof(std.math.`~fun~`(0.5f, 0.5f)) == float)
472             && is(typeof(() pure => core.stdc.math.`~fun~`f(0.5f, 0.5f))))
473         if (!__ctfe)
474         {
475             static if (T.mant_dig == float.mant_dig) return core.stdc.math.`~fun~`f(x, y);
476             else static if (T.mant_dig == double.mant_dig) return core.stdc.math.`~fun~`(x, y);
477         }
478         return std.math.`~fun~`(x, y);`;
479 
480     // Some std.math functions have appropriate return types (float,
481     // double, real) without need for a wrapper.
482     alias sqrt = std.math.sqrt;
483 
484     ///
485     T sqrt(T)(in T x) if (isFloatingPoint!T) { return std.math.sqrt(x); }
486     ///
487     T sin(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`sin`); }
488     ///
489     T cos(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`cos`); }
490     ///
491     T pow(T)(in T x, in T power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); }
492     ///
493     T powi(T)(in T x, int power) if (isFloatingPoint!T) { alias y = power; mixin(mixinCMath2!`pow`); }
494     ///
495     T exp(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`exp`); }
496     ///
497     T log(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log`); }
498     ///
499     T fabs(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`fabs`); }
500     ///
501     T floor(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`floor`); }
502     ///
503     T exp2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`exp2`); }
504     ///
505     T log10(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log10`); }
506     ///
507     T log2(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`log2`); }
508     ///
509     T ceil(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`ceil`); }
510     ///
511     T trunc(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`trunc`); }
512     ///
513     T rint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`rint`); }
514     ///
515     T nearbyint(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`nearbyint`); }
516     ///
517     T copysign(T)(in T mag, in T sgn) if (isFloatingPoint!T)
518     {
519         alias x = mag;
520         alias y = sgn;
521         mixin(mixinCMath2!`copysign`);
522     }
523     ///
524     T round(T)(in T x) if (isFloatingPoint!T) { mixin(mixinCMath!`round`); }
525     ///
526     T fmuladd(T)(in T a, in T b, in T c) if (isFloatingPoint!T) { return a * b + c; }
527     version(mir_core_test)
528     unittest { assert(fmuladd!double(2, 3, 4) == 2 * 3 + 4); }
529     ///
530     T fmin(T)(in T x, in T y) if (isFloatingPoint!T)
531     {
532         version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798
533         {
534             version (CRuntime_Microsoft)
535                 mixin(mixinCMath2!`fmin`);
536             else
537                 return std.math.fmin(x, y);
538         }
539         else
540             mixin(mixinCMath2!`fmin`);
541     }
542     ///
543     T fmax(T)(in T x, in T y) if (isFloatingPoint!T)
544     {
545         version (Windows) // https://issues.dlang.org/show_bug.cgi?id=19798
546         {
547             version (CRuntime_Microsoft)
548                 mixin(mixinCMath2!`fmax`);
549             else
550                 return std.math.fmax(x, y);
551         }
552         else
553             mixin(mixinCMath2!`fmax`);
554     }
555 
556     version (mir_core_test) @nogc nothrow pure @safe unittest
557     {
558         // Check the aliases are correct.
559         static assert(is(typeof(sqrt(1.0f)) == float));
560         auto x = sqrt!float(2.0f); // Explicit template instantiation still works.
561         auto fp = &sqrt!float; // Can still take function address.
562 
563         // Test for DMD linker problem with fmin on Windows.
564         static assert(is(typeof(fmin!float(1.0f, 1.0f))));
565         static assert(is(typeof(fmax!float(1.0f, 1.0f))));
566     }
567 }
568 
569 version (mir_core_test)
570 @nogc nothrow pure @safe unittest
571 {
572     import mir.math: PI, feqrel;
573     assert(feqrel(pow(2.0L, -0.5L), cos(PI / 4)) >= real.mant_dig - 1);
574 }
575 
576 /// Overload for cdouble, cfloat and creal
577 @optmath auto fabs(T)(in T x)
578     if (isComplex!T)
579 {
580     return x.re * x.re + x.im * x.im;
581 }
582 
583 ///
584 version(mir_core_test) unittest
585 {
586     import mir.complex;
587     assert(fabs(Complex!double(3, 4)) == 25);
588 }
589 
590 /++
591 Computes whether two values are approximately equal, admitting a maximum
592 relative difference, and a maximum absolute difference.
593 Params:
594     lhs = First item to compare.
595     rhs = Second item to compare.
596     maxRelDiff = Maximum allowable difference relative to `rhs`. Defaults to `0.5 ^^ 20`.
597     maxAbsDiff = Maximum absolute difference. Defaults to `0.5 ^^ 20`.
598         
599 Returns:
600     `true` if the two items are equal or approximately equal under either criterium.
601 +/
602 bool approxEqual(T)(const T lhs, const T rhs, const T maxRelDiff = T(0x1p-20f), const T maxAbsDiff = T(0x1p-20f))
603     if (isFloatingPoint!T)
604 {
605     if (rhs == lhs) // infs
606         return true;
607     auto diff = fabs(lhs - rhs);
608     if (diff <= maxAbsDiff)
609         return true;
610     diff /= fabs(rhs);
611     return diff <= maxRelDiff;
612 }
613 
614 ///
615 @safe pure nothrow @nogc version(mir_core_test) unittest
616 {
617     assert(approxEqual(1.0, 1.0000001));
618     assert(approxEqual(1.0f, 1.0000001f));
619     assert(approxEqual(1.0L, 1.0000001L));
620 
621     assert(approxEqual(10000000.0, 10000001));
622     assert(approxEqual(10000000f, 10000001f));
623     assert(!approxEqual(100000.0L, 100001L));
624 }
625 
626 /// ditto
627 bool approxEqual(T)(const T lhs, const T rhs, float maxRelDiff = 0x1p-20f, float maxAbsDiff = 0x1p-20f)
628     if (isComplexOf!(T, float))
629 {
630     return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff)
631         && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff);
632 }
633 
634 deprecated("Use mir.complex.approxEqual instead"):
635 
636 /// ditto
637 bool approxEqual(T)(const T lhs, const T rhs, double maxRelDiff = 0x1p-20f, double maxAbsDiff = 0x1p-20f)
638     if (isComplexOf!(T, double))
639 {
640     return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff)
641         && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff);
642 }
643 
644 /// ditto
645 bool approxEqual(T)(const T lhs, const T rhs, real maxRelDiff = 0x1p-20f, real maxAbsDiff = 0x1p-20f)
646     if (isComplexOf!(T, real))
647 {
648     return approxEqual(lhs.re, rhs.re, maxRelDiff, maxAbsDiff)
649         && approxEqual(lhs.im, rhs.im, maxRelDiff, maxAbsDiff);
650 }
651 
652 /// Complex types works as `approxEqual(l.re, r.re) && approxEqual(l.im, r.im)`
653 @safe pure nothrow @nogc version(mir_core_test) unittest
654 {
655     import mir.internal.utility: isComplexOf;
656     static struct UserComplex(T) { T re, im; }
657     alias _cdouble = UserComplex!double;
658 
659     static assert(isComplexOf!(_cdouble, double));
660 
661     assert(approxEqual(_cdouble(1.0, 1), _cdouble(1.0000001, 1), 1.0000001));
662     assert(!approxEqual(_cdouble(100000.0L, 0), _cdouble(100001L, 0)));
663 }