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 }