1 /** This module contains the $(LREF Dual) type, which is used to represent 2 dual numbers, along with related mathematical operations and functions. 3 4 The intention of the this module is to provide a dual number type, which 5 can be used for automatic differentation. This means you can calculate 6 with it the same way you would with floating point numbers. The real 7 part will be identically to the result of a calculation with floating 8 point numbers. Comparison operations (opCmp) ignore the value of the dual part. 9 10 To calculate the derivate with respect to a variable, set the dual part 11 of this variable to 1. All other dual parts should be 0. After the 12 calculation the dual part contains the derivate. 13 14 See_Also: 15 https://en.wikipedia.org/wiki/Dual_number 16 https://en.wikipedia.org/wiki/Automatic_differentiation#Automatic_differentiation_using_dual_numbers 17 18 Current_limitations: 19 "^^" operator works only for integral numbers. 20 No trigonomertic functions. 21 22 Authors: René Heldmaier and others (see individual files) 23 Copyright: Copyright (c) 2019, René Heldmaier and others (see individual files) 24 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) 25 Source: https://github.com/the5avage/dualnumbers 26 */ 27 28 /* The functions 29 30 auto dual(R)(const R re), 31 auto dual(R, D)(const R re, const D du), 32 string toString() const, 33 void toString(Writer, Char)(scope Writer w, scope const ref FormatSpec!Char formatSpec) const 34 35 and the corresponding unittests are derived from phobos std.complex 36 (github.com/dlang/phobos/blob/master/std/complex.d). 37 See comments for more details 38 */ 39 40 module dualnumbers; 41 42 import std.traits; 43 import std.math; 44 45 /* The following two functions and the corresponding unittest are derived from 46 phobos std.complex (github.com/dlang/phobos/blob/master/std/complex.d) 47 Authors: Lars Tandle Kyllingstad, Don Clugston 48 Copyright: Copyright (c) 2010, Lars T. Kyllingstad. 49 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) 50 Modified by: René Heldmaier 51 */ 52 /** Helper function that returns a dual number with the specified 53 real and dual parts. 54 Params: 55 R = (template parameter) type of real part of dual number 56 D = (template parameter) type of dual part of dual number 57 re = real part of complex number to be constructed 58 du = (optional) dual part of complex number, 0 if omitted. 59 Returns: 60 `Dual` instance with real and dual parts set 61 to the values provided as input. If neither `re` nor 62 `du` are floating-point numbers, the return type will 63 be `Dual!double`. Otherwise, the return type is 64 deduced using $(D std.traits.CommonType!(R, D)). 65 */ 66 auto dual(R)(const R re) @safe pure nothrow @nogc 67 if (is(R : double)) 68 { 69 static if (isFloatingPoint!R) 70 return Dual!R(re, 0); 71 else 72 return Dual!double(re, 0); 73 } 74 75 /// ditto 76 auto dual(R, D)(const R re, const D du) @safe pure nothrow @nogc 77 if (is(R : double) && is(D : double)) 78 { 79 static if (isFloatingPoint!R || isFloatingPoint!D) 80 return Dual!(CommonType!(R, D))(re, du); 81 else 82 return Dual!double(re, du); 83 } 84 85 /// 86 @safe pure nothrow unittest 87 { 88 auto a = dual(1.0); 89 static assert(is(typeof(a) == Dual!double)); 90 assert(a.re == 1.0); 91 assert(a.du == 0.0); 92 93 auto b = dual(2.0L); 94 static assert(is(typeof(b) == Dual!real)); 95 assert(b.re == 2.0L); 96 assert(b.du == 0.0L); 97 98 auto c = dual(1.0, 2.0); 99 static assert(is(typeof(c) == Dual!double)); 100 assert(c.re == 1.0); 101 assert(c.du == 2.0); 102 103 auto d = dual(3.0, 4.0L); 104 static assert(is(typeof(d) == Dual!real)); 105 assert(d.re == 3.0); 106 assert(d.du == 4.0L); 107 108 auto e = dual(1); 109 static assert(is(typeof(e) == Dual!double)); 110 assert(e.re == 1); 111 assert(e.du == 0); 112 113 auto f = dual(1L, 2); 114 static assert(is(typeof(f) == Dual!double)); 115 assert(f.re == 1L); 116 assert(f.du == 2); 117 118 auto g = dual(3, 4.0L); 119 static assert(is(typeof(g) == Dual!real)); 120 assert(g.re == 3); 121 assert(g.du == 4.0L); 122 } 123 124 /// Dual number for automatic differentation 125 struct Dual(T) if (isFloatingPoint!T) 126 { 127 import std.format : FormatSpec; 128 import std.range.primitives : isOutputRange; 129 130 /// The real part 131 T re; 132 /// The dual part 133 T du; 134 135 /* The following two functions and the corresponding unittest are derived from 136 phobos std.complex (github.com/dlang/phobos/blob/master/std/complex.d) 137 Authors: Lars Tandle Kyllingstad, Don Clugston 138 Copyright: Copyright (c) 2010, Lars T. Kyllingstad. 139 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) 140 Modified by: René Heldmaier 141 */ 142 /** Converts the dual number to a string representation. 143 The second form of this function is usually not called directly; 144 instead, it is used via $(REF format, std,string), as shown in the examples 145 below. Supported format characters are 'e', 'f', 'g', 'a', and 's'. 146 See the $(MREF std, format) and $(REF format, std,string) 147 documentation for more information. 148 */ 149 string toString() const @safe /* TODO: pure nothrow */ 150 { 151 import std.exception : assumeUnique; 152 char[] buf; 153 buf.reserve(100); 154 auto fmt = FormatSpec!char("%s"); 155 toString((const(char)[] s) { buf ~= s; }, fmt); 156 static trustedAssumeUnique(T)(T t) @trusted { return assumeUnique(t); } 157 return trustedAssumeUnique(buf); 158 } 159 160 /// 161 @safe unittest 162 { 163 auto c = dual(1.2, 3.4); 164 165 // Vanilla toString formatting: 166 assert(c.toString() == "1.2+3.4ε"); 167 168 // Formatting with std.string.format specs: the precision and width 169 // specifiers apply to both the real and imaginary parts of the 170 // complex number. 171 import std.format : format; 172 assert(format("%.2f", c) == "1.20+3.40ε"); 173 assert(format("%4.1f", c) == " 1.2+ 3.4ε"); 174 } 175 176 /// ditto 177 void toString(Writer, Char)(scope Writer w, scope const ref FormatSpec!Char formatSpec) const 178 if (isOutputRange!(Writer, const(Char)[])) 179 { 180 import std.format : formatValue; 181 import std.math : signbit; 182 import std.range.primitives : put; 183 formatValue(w, re, formatSpec); 184 if (signbit(du) == 0) 185 put(w, "+"); 186 formatValue(w, du, formatSpec); 187 put(w, "ε"); 188 } 189 190 @safe pure nothrow @nogc: 191 192 /** 193 Construct a dual number with the specified real and 194 dual parts. In the case where a single argument is passed 195 that is not a dual number, the imaginary part of the result will be 196 zero. 197 */ 198 this(R : T)(const Dual!R d) 199 { 200 re = d.re; 201 du = d.du; 202 } 203 204 /// ditto 205 this(R : T)(const R re) 206 { 207 this.re = re; 208 du = 0.0; 209 } 210 211 /// ditto 212 this(Rr : T, Rd : T)(const Rr re, const Rd du) 213 { 214 this.re = re; 215 this.du = du; 216 } 217 218 /* assignment operators */ 219 220 // this = numeric 221 ref Dual opAssign(R : T)(const R rhs) 222 { 223 this.re = rhs; 224 this.du = 0.0; 225 return this; 226 } 227 228 // this = dual 229 ref Dual opAssign(R : T)(const Dual!R rhs) 230 { 231 re = rhs.re; 232 du = rhs.du; 233 return this; 234 } 235 236 /* unary operators */ 237 238 // unary + and - 239 Dual opUnary(string op)() const 240 if (op == "+" || op == "-") 241 { 242 return mixin("Dual(" ~op~ "re," ~op~ "du)"); 243 } 244 245 /* binary operators */ 246 247 // dual + dual, dual - dual 248 Dual!(CommonType!(T, R)) opBinary(string op, R)(const Dual!R rhs) const 249 if (op == "+" || op == "-") 250 { 251 alias D = typeof(return); 252 return mixin("D(re" ~ op ~ "rhs.re, du" ~ op ~ "rhs.du)"); 253 } 254 255 // dual * dual 256 Dual!(CommonType!(T, R)) opBinary(string op, R)(const Dual!R rhs) const 257 if (op == "*") 258 { 259 alias D = typeof(return); 260 return D(re * rhs.re, du * rhs.re + re * rhs.du); 261 } 262 263 // dual / dual 264 Dual!(CommonType!(T, R)) opBinary(string op, R)(const Dual!R rhs) const 265 if (op == "/") 266 { 267 alias D = typeof(return); 268 return D(re/rhs.re, (rhs.re*du - rhs.du*re)/rhs.re/rhs.re); 269 } 270 271 // dual + numeric, dual - numeric 272 Dual!(CommonType!(T, R)) opBinary(string op, R)(const R rhs) const 273 if ((op == "+" || op == "-") && isNumeric!R) 274 { 275 alias D = typeof(return); 276 return mixin("D(re" ~op~ "rhs, du)"); 277 } 278 279 // dual * numeric, dual / numeric 280 Dual!(CommonType!(T, R)) opBinary(string op, R)(const R rhs) const 281 if ((op == "*" || op == "/") && isNumeric!R) 282 { 283 alias D = typeof(return); 284 return mixin("D(re" ~op~ "rhs, du" ~op~ "rhs)"); 285 } 286 287 // numeric * dual, numeric + dual 288 Dual!(CommonType!(T, R)) opBinaryRight(string op, R)(const R lhs) const 289 if ((op == "+" || op == "*") && isNumeric!R) 290 { 291 return opBinary!op(lhs); 292 } 293 294 // numeric - dual 295 Dual!(CommonType!(T, R)) opBinaryRight(string op, R)(const R lhs) const 296 if ((op == "-") && isNumeric!R) 297 { 298 alias D = typeof(return); 299 return D(lhs - re, -du); 300 } 301 302 // numeric / dual 303 Dual!(CommonType!(T, R)) opBinaryRight(string op, R)(const R lhs) const 304 if ((op == "/") && isNumeric!R) 305 { 306 alias D = typeof(return); 307 return D(lhs/re, -du*lhs/re/re); 308 } 309 310 // dual ^^ integer 311 Dual!T opBinary(string op, R)(R rhs) const 312 if (op == "^^" && isIntegral!R) 313 { 314 if (rhs > 0) { 315 Dual!T result = this; 316 while (--rhs) { 317 result *= this; 318 } 319 return result; 320 } else if (rhs < 0) { 321 auto result = Dual!T(1.0); 322 while (rhs++) { 323 result /= this; 324 } 325 return result; 326 } 327 return Dual!T(1.0); 328 } 329 330 /* op-assign operators */ 331 332 // dual += dual, dual -= dual, dual *= dual, dual /= dual 333 ref Dual opOpAssign(string op, D)(const D rhs) 334 if ((op == "+" || op == "-" || op == "*" || op == "/") && is (D R == Dual!R)) 335 { 336 mixin("this = this " ~op~ "rhs;"); 337 return this; 338 } 339 340 // dual ^^= numeric 341 ref Dual opOpAssign(string op, R : T)(R rhs) 342 if (op == "^^") 343 { 344 this = this ^^ rhs; 345 return this; 346 } 347 348 // dual += numeric, dual -= numeric, dual *= numeric, dual /= numeric 349 ref Dual opOpAssign(string op, R : T)(const R rhs) 350 if (op == "+" || op == "-" || op == "*" || op =="/") 351 { 352 mixin("this = this " ~op~ "rhs;"); 353 return this; 354 } 355 356 /* comparison operators 357 Ignore dual part because dual numbers shall behave identically 358 to normal numbers (they are made for automatic differentation). 359 */ 360 361 // this == dual 362 bool opEquals(R : T)(const Dual!R z) const 363 { 364 return re == z.re && du == z.du; 365 } 366 367 // this == numeric 368 bool opEquals(R : T)(const R r) const 369 { 370 return re == r && du == 0; 371 } 372 373 // this == dual 374 int opCmp(R : T)(const Dual!R z) const 375 { 376 if (re < z.re) 377 return -1; 378 else if (re > z.re) 379 return 1; 380 else 381 return 0; 382 } 383 384 // this == dual 385 int opCmp(R : T)(const R r) const 386 { 387 if (re < r) 388 return -1; 389 else if (re > r) 390 return 1; 391 else 392 return 0; 393 } 394 } 395 396 /** To calculate the derivate of a function, set the dual part of the variable 397 by which you want to derive to 1. Other dual numbers should have dual part of 0. 398 Then just calculate the function like you would with normal numbers. 399 The real part is always identically to the result with normal numbers. 400 After the calculation the dual part holds the derivate of the function. 401 */ 402 unittest 403 { 404 import std.math: approxEqual; 405 // f(x) = x⁵ f'(x) = 5x⁴ for x = 2 406 const x = Dual!double(2.0, 1.0); 407 auto f2 = x^^5; 408 assert(f2.re.approxEqual(2.0^^5) && f2.du.approxEqual(5.0*2.0^^4)); 409 410 // f(x) = 3x² f'(x) = 6x for x = 2 411 f2 = 3.0 * x * x; 412 assert(approxEqual(f2.re, 3.0*2.0*2.0) && approxEqual(f2.du, 6.0*2.0)); 413 414 // f(x) = 3/(1-x) f'(x) = 3/(1-x)^2 for x = 2 415 f2 = 3.0/(1.0 - x); 416 assert(f2.re.approxEqual(-3.0) && f2.du.approxEqual(3.0)); 417 418 // f(x) = 3exp(2x), f'(x) = 6exp(2x) for x = 2 419 f2 = 3 * exp(2 * x); 420 import std.math: exp; 421 assert(f2.re.approxEqual(3 * exp(4.0)) && f2.du.approxEqual(6 * exp(4.0))); 422 } 423 424 /** Exponential function on dual numbers. 425 426 Params: x = A dual number. 427 Returns: exp(x) 428 */ 429 Dual!T exp(T)(Dual!T x) @safe pure nothrow @nogc 430 { 431 import std.math: exp; 432 Dual!T result = void; 433 result.re = exp(x.re); 434 result.du = result.re * x.du; 435 return result; 436 } 437 438 /// 439 unittest 440 { 441 import std.math: approxEqual; 442 // f(x) = exp(x), f'(x) = exp(x) 443 auto x = dual(5.0, 1.0); 444 auto res = x.exp(); 445 assert(res.re.approxEqual(res.du)); 446 447 // f(x) = exp(3x), f'(x) = 3*exp(3x) 448 res = exp(3 * x); 449 assert(res.du.approxEqual(3 * res.re)); 450 } 451 452 /* Test constructors */ 453 454 // Works with floating point types 455 unittest 456 { 457 Dual!float df; 458 Dual!double dd; 459 Dual!real dr; 460 assert(df.sizeof == 2*float.sizeof); 461 assert(dd.sizeof == 2*double.sizeof); 462 assert(dr.sizeof == 2*real.sizeof); 463 } 464 465 // Constructor with one parameter 466 unittest 467 { 468 import std.math: approxEqual; 469 Dual!double d = Dual!double(33.3); 470 assert(d.re == 33.3 && d.du == 0.0); 471 Dual!real dr = Dual!real(44.4); 472 assert(dr.re.approxEqual(44.4) && dr.du.approxEqual(0.0)); 473 } 474 475 // Constructor with two parameters 476 unittest 477 { 478 import std.math: approxEqual; 479 Dual!double d = Dual!double(1.1, 2.2); 480 assert(d.re.approxEqual(1.1) && d.du.approxEqual(2.2)); 481 } 482 483 // Construct from other dual 484 unittest 485 { 486 import std.math: approxEqual; 487 const d = Dual!double(1.0, 2.0); 488 Dual!float f = d; 489 assert(d.re.approxEqual(f.re) && d.du.approxEqual(f.du)); 490 } 491 492 /* Test operators */ 493 494 // dual = numeric 495 unittest 496 { 497 import std.math: approxEqual; 498 immutable f = 4.0; 499 auto d = Dual!double(0.9, 0.9); 500 auto d2 = d = f; 501 assert(d2.re.approxEqual(4.0) && d2.du.approxEqual(0.0)); 502 } 503 504 // dual = dual 505 unittest 506 { 507 auto d = Dual!double(1.1, 1.1); 508 const d2 = Dual!double(2.2, 3.3); 509 d = d2; 510 assert(d.re == 2.2 && d.du == 3.3); 511 } 512 513 // Chain assignment 514 unittest 515 { 516 import std.math: approxEqual; 517 Dual!double dd = Dual!double(1.1, 2.2); 518 Dual!float df; 519 df = dd = Dual!real(1.1, 2.2); 520 assert(df.re.approxEqual(1.1) && df.du.approxEqual(2.2)); 521 } 522 523 // unary + and - 524 unittest 525 { 526 import std.math: approxEqual; 527 const d = Dual!double(1.1, 2.2); 528 Dual!double dp = +d; 529 assert(approxEqual(dp.re, 1.1) && approxEqual(dp.du, 2.2)); 530 Dual!double dm = -d; 531 assert(approxEqual(dm.re, -1.1) && approxEqual(dm.du, -2.2)); 532 } 533 534 // dual + dual 535 unittest 536 { 537 import std.math: approxEqual; 538 auto d = Dual!double(1.1, 2.2); 539 const d2 = Dual!float(3.3, 4.4); 540 d = d2 + d; 541 assert(approxEqual(d.re, 4.4) && approxEqual(d.du, 6.6)); 542 } 543 544 // dual - dual 545 unittest 546 { 547 import std.math: approxEqual; 548 auto d = Dual!double(1.1, 2.2); 549 const d2 = Dual!float(3.3, 5.5); 550 d = d2 - d; 551 assert(approxEqual(d.re, 2.2) && approxEqual(d.du, 3.3)); 552 } 553 554 // dual + numeric, numeric + dual 555 unittest 556 { 557 import std.math: approxEqual; 558 const d = Dual!double(1.1, 2.2); 559 auto res = d + 5.0; 560 assert(approxEqual(res.re, 6.1) && approxEqual(res.du, 2.2)); 561 res = 1.0 + d; 562 assert(approxEqual(res.re, 2.1) && approxEqual(res.du, 2.2)); 563 } 564 565 // dual - numeric, numeric - dual 566 unittest 567 { 568 import std.math: approxEqual; 569 const d = Dual!double(1.1, 2.2); 570 auto res = d - 5.0; 571 assert(approxEqual(res.re, -3.9) && approxEqual(res.du, 2.2)); 572 res = 1.0 - d; 573 assert(approxEqual(res.re, -0.1) && approxEqual(res.du, -2.2)); 574 } 575 576 // dual * dual 577 unittest 578 { 579 import std.math: approxEqual; 580 const a = Dual!double(1.1, 2.2); 581 const b = Dual!float(3.3, 4.4); 582 Dual!double d = a * b; 583 assert(approxEqual(d.re, 1.1 * 3.3) && approxEqual(d.du, 1.1*4.4 + 2.2*3.3)); 584 } 585 586 // dual / dual 587 unittest 588 { 589 import std.math: approxEqual; 590 const a = Dual!double(1.1, 2.2); 591 const b = Dual!float(3.3, 4.4); 592 Dual!double d = a / b; 593 assert(approxEqual(d.re, 1.1 / 3.3) && approxEqual(d.du, (3.3*2.2 - 1.1*4.4)/3.3/3.3)); 594 } 595 596 // dual * numeric 597 unittest 598 { 599 import std.math: approxEqual; 600 Dual!double d = Dual!double(1.1, 2.2); 601 d = d * 3.0; 602 assert(approxEqual(d.re, 3.3) && approxEqual(d.du, 6.6)); 603 } 604 605 // numeric * dual 606 unittest 607 { 608 import std.math: approxEqual; 609 const d = Dual!double(1.1, 2.2); 610 const res = 3.0 * d; 611 assert(approxEqual(res.re, 3.3) && approxEqual(res.du, 6.6)); 612 } 613 614 // dual / numeric 615 unittest 616 { 617 import std.math: approxEqual; 618 const d = Dual!double(1.1, 2.2); 619 const res = d / 3.0; 620 assert(approxEqual(res.re, 1.1/3.0) && approxEqual(res.du, 2.2/3.0)); 621 } 622 623 // numeric / dual 624 unittest 625 { 626 import std.math: approxEqual; 627 const d = Dual!double(1.1, 2.2); 628 Dual!double expect = Dual!double(3.0, 0.0); 629 Dual!double res = 3.0 / d; 630 expect = expect / d; 631 assert(approxEqual(res.re, expect.re) && approxEqual(res.du, expect.du)); 632 } 633 634 // dual += dual 635 unittest 636 { 637 import std.math: approxEqual; 638 const d = Dual!double(1.1, 2.2); 639 Dual!double res = d; 640 res += d; 641 assert(res.re.approxEqual(2.2) && res.du.approxEqual(4.4)); 642 } 643 644 // dual -= dual 645 unittest 646 { 647 import std.math: approxEqual; 648 const d = Dual!double(1.1, 2.2); 649 auto res = Dual!double(0.5, 0.7); 650 res -= d; 651 assert(res.re.approxEqual(-0.6) && res.du.approxEqual(-1.5)); 652 } 653 654 // dual *= dual 655 unittest 656 { 657 import std.math: approxEqual; 658 const d = Dual!double(1.1, 2.2); 659 auto res = Dual!double(0.5, 0.7); 660 auto expect = res * d; 661 res *= d; 662 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 663 } 664 665 // dual /= dual 666 unittest 667 { 668 import std.math: approxEqual; 669 immutable d = Dual!double(1.1, 2.2); 670 auto res = Dual!double(0.5, 0.7); 671 auto expect = res / d; 672 res /= d; 673 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 674 } 675 676 // dual += numeric 677 unittest 678 { 679 import std.math: approxEqual; 680 auto res = Dual!double(0.5, 0.7); 681 res += 5.0; 682 assert(res.re.approxEqual(5.5) && res.du.approxEqual(0.7)); 683 } 684 685 // dual -= numeric 686 unittest 687 { 688 import std.math: approxEqual; 689 auto res = Dual!double(0.5, 0.7); 690 res -= 5.0; 691 assert(res.re.approxEqual(-4.5) && res.du.approxEqual(0.7)); 692 } 693 694 // dual *= numeric 695 unittest 696 { 697 import std.math: approxEqual; 698 auto res = Dual!double(0.5, 0.7); 699 auto expect = res * 5.0; 700 res *= 5.0; 701 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 702 } 703 704 // dual /= numeric 705 unittest 706 { 707 import std.math: approxEqual; 708 auto res = Dual!double(0.5, 0.7); 709 auto expect = res / 5.0; 710 res /= 5.0; 711 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 712 } 713 714 // dual ^^= numeric 715 unittest 716 { 717 import std.math: approxEqual; 718 auto res = Dual!double(0.5, 0.7); 719 auto expect = res ^^ 5; 720 res ^^= 5; 721 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 722 } 723 724 // dual ^^ positive integer 725 unittest 726 { 727 import std.math: approxEqual; 728 long exponent = 4; 729 const d = Dual!double(2.2, 3.3); 730 const expect = d * d * d * d; 731 const res = d ^^ exponent; 732 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 733 } 734 735 // dual ^^ 0 736 unittest 737 { 738 import std.math: approxEqual; 739 short exponent = 0; 740 const d = Dual!double(3.3, 2.2); 741 const res = d ^^ exponent; 742 assert(res.re.approxEqual(1.0) && res.du.approxEqual(0.0)); 743 } 744 745 // dual ^^ negative integer 746 unittest 747 { 748 import std.math: approxEqual; 749 int exponent = -4; 750 const d = Dual!double(2.2, 3.3); 751 const expect = 1.0 / d / d / d / d; 752 const res = d ^^ exponent; 753 assert(res.re.approxEqual(expect.re) && res.du.approxEqual(expect.du)); 754 } 755 756 // opEquals 757 unittest 758 { 759 const a = Dual!double(1.1, 2.2); 760 const b = Dual!double(1.1, 3.3); 761 const c = a; 762 const d = Dual!float(1.1f, 0.0f); 763 assert(a != b && a == c); 764 assert(d == 1.1f && a != 1.1); 765 } 766 767 // opCmp 768 unittest 769 { 770 const a = Dual!double(1.1, 100.0); 771 const b = Dual!double(2.0, -30.0); 772 assert(a < b && a <= b); 773 assert(b > a && b >= a); 774 assert(a <= a && a >= a); 775 assert(a < 2.0 && a <= 2.0); 776 assert(2.0 > a && 2.0 >= a); 777 assert(a <= 1.1 && a >= 1.1); 778 }