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 }