1 
2 /** Bounded arithmetic wrapper type, similar to Ada's range/interval types.
3 
4     Copyright: Per Nordlöw 2018-.
5     License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
6     Authors: $(WEB Per Nordlöw)
7 
8     See_Also: http://en.wikipedia.org/wiki/Interval_arithmetic
9     See_Also: https://bitbucket.org/davidstone/bounded_integer
10     See_Also: http://stackoverflow.com/questions/18514806/ada-like-types-in-nimrod
11     See_Also: http://forum.dlang.org/thread/xogeuqdwdjghkklzkfhl@forum.dlang.org#post-rksboytciisyezkapxkr:40forum.dlang.org
12     See_Also: http://forum.dlang.org/thread/lxdtukwzlbmzebazusgb@forum.dlang.org#post-ymqdbvrwoupwjycpizdi:40forum.dlang.org
13     See_Also: http://dlang.org/operatoroverloading.html
14 
15     TODO: Test with geometry.Vector or geometry.Point
16 
17     TODO: Make stuff @safe pure @nogc and in some case nothrow
18 
19     TODO: Implement overload for conditional operator p ? x1 : x2
20     TODO: Propagate ranges in arithmetic (opUnary, opBinary, opOpAssign):
21           - Integer: +,-,*,^^,/
22           - FloatingPoint: +,-,*,/,^^,sqrt,
23 
24     TODO: Should implicit conversions to un-Bounds be allowed?
25     Not in https://bitbucket.org/davidstone/bounded_integer.
26 
27     TODO: Merge with limited
28     TODO: Is this a good idea to use?:
29     import std.meta;
30     mixin Proxy!_t;             // Limited acts as V (almost).
31     invariant() {
32     enforce(_t >= low && _t <= high);
33     wln("fdsf");
34 
35     TODO: If these things take to long to evaluted at compile-time maybe we need
36     to build it into the language for example using a new syntax either using
37     - integer(range:low..high, step:1)
38     - int(range:low..high, step:1)
39     - num(range:low..high, step:1)
40 
41     TODO: Use
42     V saveOp(string op, V)(V x, V y) pure @save @nogc if(isIntegral!V
43     && (op=="+" || op=="-" || op=="<<" || op=="*"))
44     {
45     mixin("x "~op~"= y");
46     static if(isSigned!V)
47     {
48     static if(op == "*")
49     {
50     asm naked { jnc opok; }
51     }
52     else
53     {
54     asm naked { jno opok; }
55     }
56     x = V.min;
57     }
58     else // unsigned
59     {
60     asm naked { jnc opok; }
61     x = V.max;
62     }
63     opok:
64     return x;
65     }
66 
67     TODO: Reuse core.checkedint
68 
69     TODO: Move to Phobos std.typecons
70  */
71 module nxt.bound;
72 
73 version(none):
74 
75 import std.conv: to;
76 import std.traits: CommonType, isIntegral, isUnsigned, isSigned, isFloatingPoint, isSomeChar, isScalarType, isBoolean;
77 import nxt.traits_ex : haveCommonType;
78 import std.stdint: intmax_t;
79 import std.exception: assertThrown;
80 
81 version = print;
82 
83 version(print) import std.stdio: wln = writeln;
84 
85 /** TODO: Use boundness policy. */
86 enum Policy { clamped, overflowed, throwed, modulo }
87 
88 //     TODO: Do we need a specific underflow Exception?
89 // class BoundUnderflowException : Exception {
90 //     this(string msg) { super(msg); }
91 // }
92 
93 /** Exception thrown when `Bound` values overflows or underflows. */
94 class BoundOverflowException : Exception
95 {
96     this(string msg) { super(msg); }
97 }
98 
99 /** Check if the value of `expr` is known at compile-time.
100     See_Also: http://forum.dlang.org/thread/owlwzvidwwpsrelpkbok@forum.dlang.org
101 */
102 enum isCTEable(alias expr) = __traits(compiles, { enum id = expr; });
103 
104 /** Check if type `T` can wrapped in a `Bounded`.
105  */
106 enum isBoundable(T) = isScalarType!T;
107 
108 /** Check if expression `expr` is a compile-time-expression that can be used as a `Bound`.
109  */
110 enum isCTBound(alias expr) = (isBoundable!(typeof(expr)) &&
111                               isCTEable!expr);
112 
113 /** TODO: use this. */
114 enum areCTBoundable(alias low, alias high) = (isCTBound!low &&
115                                               isCTBound!high &&
116                                               low < high);
117 
118 /* TODO: Is there a already a Phobos trait or builtin property for this? */
119 template PackedNumericType(alias expr)
120 if (isCTBound!expr)
121 {
122     alias Type = typeof(expr);
123     static if (isIntegral!Type)
124     {
125         static if (expr < 0)
126         {
127             static      if (expr >= -0x80               && high <= 0x7f)               { alias PackedNumericType = byte; }
128             else static if (expr >= -0x8000             && high <= 0x7fff)             { alias PackedNumericType = short; }
129             else static if (expr >= -0x80000000         && high <= 0x7fffffff)         { alias PackedNumericType = int; }
130             else static if (expr >= -0x8000000000000000 && high <= 0x7fffffffffffffff) { alias PackedNumericType = long; }
131             else { alias PackedNumericType = Type; }
132         }
133         else                    // positive
134         {
135             static      if (expr <= 0xff)               { alias PackedNumericType = ubyte; }
136             else static if (expr <= 0xffff)             { alias PackedNumericType = ushort; }
137             else static if (expr <= 0xffffffff)         { alias PackedNumericType = uint; }
138             else static if (expr <= 0xffffffffffffffff) { alias PackedNumericType = ulong; }
139             else { alias PackedNumericType = Type; }
140         }
141     }
142     else // no special handling for Boolean, FloatingPoint, SomeChar for now
143     {
144         alias PackedNumericType = Type;
145     }
146 }
147 
148 /** Get type that can contain the inclusive bound [`low`, `high`].
149     If `packed` optimize storage for compactness otherwise for speed.
150     If `signed` use a signed integer.
151 */
152 template BoundsType(alias low,
153                     alias high,
154                     bool packed = true,
155                     bool signed = false)
156 if (isCTBound!low &&
157         isCTBound!high)
158 {
159     static assert(low != high,
160                   "low == high: use an enum instead");
161     static assert(low < high,
162                   "Requires low < high, low = " ~
163                   to!string(low) ~ " and high = " ~ to!string(high));
164 
165     alias LowType = typeof(low);
166     alias HighType = typeof(high);
167 
168     enum span = high - low;
169     alias SpanType = typeof(span);
170 
171     static if (isIntegral!LowType &&
172                isIntegral!HighType)
173     {
174         static if (signed &&
175                    low < 0)    // negative
176         {
177             static if (packed)
178             {
179                 static      if (low >= -0x80               && high <= 0x7f)               { alias BoundsType = byte; }
180                 else static if (low >= -0x8000             && high <= 0x7fff)             { alias BoundsType = short; }
181                 else static if (low >= -0x80000000         && high <= 0x7fffffff)         { alias BoundsType = int; }
182                 else static if (low >= -0x8000000000000000 && high <= 0x7fffffffffffffff) { alias BoundsType = long; }
183                 else { alias BoundsType = CommonType!(LowType, HighType); }
184             }
185             else
186                 alias BoundsType = CommonType!(LowType, HighType);
187         }
188         else                    // positive
189         {
190             static if (packed)
191             {
192                 static      if (span <= 0xff)               { alias BoundsType = ubyte; }
193                 else static if (span <= 0xffff)             { alias BoundsType = ushort; }
194                 else static if (span <= 0xffffffff)         { alias BoundsType = uint; }
195                 else static if (span <= 0xffffffffffffffff) { alias BoundsType = ulong; }
196                 else { alias BoundsType = CommonType!(LowType, HighType); }
197             }
198             else
199                 alias BoundsType = CommonType!(LowType, HighType);
200         }
201     }
202     else static if (isFloatingPoint!LowType &&
203                     isFloatingPoint!HighType)
204         alias BoundsType = CommonType!(LowType, HighType);
205     else static if (isSomeChar!LowType &&
206                     isSomeChar!HighType)
207         alias BoundsType = CommonType!(LowType, HighType);
208     else static if (isBoolean!LowType &&
209                     isBoolean!HighType)
210         alias BoundsType = CommonType!(LowType, HighType);
211     else
212         static assert(0, "Cannot construct a bound using types " ~ LowType.stringof ~ " and " ~ HighType.stringof);
213 }
214 
215 unittest
216 {
217     assertThrown(('a'.bound!(false, true)));
218     assertThrown((false.bound!('a', 'z')));
219     //wln(false.bound!('a', 'z')); // TODO: Should this give compile-time error?
220 
221     static assert(!__traits(compiles, { alias IBT = BoundsType!(0, 0); }));  // disallow
222     static assert(!__traits(compiles, { alias IBT = BoundsType!(1, 0); })); // disallow
223 
224     static assert(is(typeof(false.bound!(false, true)) == Bound!(bool, false, true)));
225     static assert(is(typeof('p'.bound!('a', 'z')) == Bound!(char, 'a', 'z')));
226 
227     // low < 0
228     static assert(is(BoundsType!(-1, 0, true, true) == byte));
229     static assert(is(BoundsType!(-1, 0, true, false) == ubyte));
230     static assert(is(BoundsType!(-0xff, 0, true, false) == ubyte));
231     static assert(is(BoundsType!(-0xff, 1, true, false) == ushort));
232 
233     static assert(is(BoundsType!(byte.min, byte.max, true, true) == byte));
234     static assert(is(BoundsType!(byte.min, byte.max + 1, true, true) == short));
235 
236     static assert(is(BoundsType!(short.min, short.max, true, true) == short));
237     static assert(is(BoundsType!(short.min, short.max + 1, true, true) == int));
238 
239     // low == 0
240     static assert(is(BoundsType!(0, 0x1) == ubyte));
241     static assert(is(BoundsType!(ubyte.min, ubyte.max) == ubyte));
242 
243     static assert(is(BoundsType!(ubyte.min, ubyte.max + 1) == ushort));
244     static assert(is(BoundsType!(ushort.min, ushort.max) == ushort));
245 
246     static assert(is(BoundsType!(ushort.min, ushort.max + 1) == uint));
247     static assert(is(BoundsType!(uint.min, uint.max) == uint));
248 
249     static assert(is(BoundsType!(uint.min, uint.max + 1UL) == ulong));
250     static assert(is(BoundsType!(ulong.min, ulong.max) == ulong));
251 
252     // low > 0
253     static assert(is(BoundsType!(ubyte.max, ubyte.max + ubyte.max) == ubyte));
254     static assert(is(BoundsType!(ubyte.max, ubyte.max + 0x100) == ushort));
255     static assert(is(BoundsType!(uint.max + 1UL, uint.max + 1UL + ubyte.max) == ubyte));
256     static assert(!is(BoundsType!(uint.max + 1UL, uint.max + 1UL + ubyte.max + 1) == ubyte));
257 
258     // floating point
259     static assert(is(BoundsType!(0.0, 10.0) == double));
260 }
261 
262 /** Value of type `V` bound inside inclusive range [`low`, `high`].
263 
264     If `optional` is `true`, this stores one extra undefined state (similar to
265     Haskell's `Maybe`).
266 
267     If `useExceptions` is true range errors will throw a `BoundOverflowException`,
268     otherwise truncation plus warnings will issued.
269 */
270 struct Bound(V,
271              alias low,
272              alias high,
273              bool optional = false,
274              bool useExceptions = true,
275              bool packed = true,
276              bool signed = false)
277 if (isBoundable!V)
278 {
279     /* Requirements */
280     static assert(low <= high,
281                   "Requirement not fulfilled: low < high, low = " ~
282                   to!string(low) ~ " and high = " ~ to!string(high));
283     static if (optional)
284         static assert(high + 1 == V.max,
285                       "high + 1 cannot equal V.max");
286 
287     /** Get low inclusive bound. */
288     static auto min() @property @safe pure nothrow
289     {
290         return low;
291     }
292 
293     /** Get high inclusive bound. */
294     static auto max() @property @safe pure nothrow
295     {
296         return optional ? high - 1 : high;
297     }
298 
299     static if (isIntegral!V && low >= 0)
300     {
301         size_t opCast(U : size_t)() const { return this._value; } // for IndexedBy support
302     }
303 
304     /** Construct from unbounded value `rhs`. */
305     this(U, string file = __FILE__, int line = __LINE__)(U rhs)
306         if (isBoundable!(U))
307     {
308         checkAssign!(U, file, line)(rhs);
309         this._value = cast(V)(rhs - low);
310     }
311     /** Assigne from unbounded value `rhs`. */
312     auto opAssign(U, string file = __FILE__, int line = __LINE__)(U rhs)
313         if (isBoundable!(U))
314     {
315         checkAssign!(U, file, line)(rhs);
316         _value = rhs - low;
317         return this;
318     }
319 
320     bool opEquals(U)(U rhs) const
321         if (is(typeof({ auto _ = V.init == U.init; })))
322     {
323         return value() == rhs;
324     }
325 
326     /** Construct from `Bound` value `rhs`. */
327     this(U,
328          alias low_,
329          alias high_)(Bound!(U, low_, high_,
330                              optional, useExceptions, packed, signed) rhs)
331         if (low <= low_ &&
332             high_ <= high)
333     {
334         // verified at compile-time
335         this._value = rhs._value + (high - high_);
336     }
337 
338     /** Assign from `Bound` value `rhs`. */
339     auto opAssign(U,
340                   alias low_,
341                   alias high_)(Bound!(U, low_, high_,
342                                       optional, useExceptions, packed, signed) rhs)
343         if (low <= low_ &&
344             high_ <= high &&
345             haveCommonType!(V, U))
346     {
347         // verified at compile-time
348         this._value = rhs._value + (high - high_);
349         return this;
350     }
351 
352     auto opOpAssign(string op, U, string file = __FILE__, int line = __LINE__)(U rhs)
353         if (haveCommonType!(V, U))
354     {
355         CommonType!(V, U) tmp = void;
356         mixin("tmp = _value " ~ op ~ "rhs;");
357         mixin(check());
358         _value = cast(V)tmp;
359         return this;
360     }
361 
362     @property auto value() inout
363     {
364         return _value + this.min;
365     }
366 
367     @property void toString(scope void delegate(scope const(char)[]) @safe sink) const
368     {
369         import std.format : formattedWrite;
370         sink.formattedWrite!"%s ∈ [%s, %s] ⟒ %s"(this.value,
371                                                  min,
372                                                  max,
373                                                  V.stringof);
374     }
375 
376     /** Check if this value is defined. */
377     @property bool isDefined() @safe const pure nothrow @nogc
378     {
379         return optional ? this.value != V.max : true;
380     }
381 
382     /** Check that last operation was a success. */
383     static string check() @trusted pure @nogc
384     {
385         return q{
386             asm { jo overflow; }
387             if (value < min)
388                 goto overflow;
389             if (value > max)
390                 goto overflow;
391             goto ok;
392           // underflow:
393           //   immutable uMsg = "Underflow at " ~ file ~ ":" ~ to!string(line) ~ " (payload: " ~ to!string(value) ~ ")";
394           //   if (useExceptions) {
395           //       throw new BoundUnderflowException(uMsg);
396           //   } else {
397           //       wln(uMsg);
398           //   }
399           overflow:
400             throw new BoundOverflowException("Overflow at " ~ file ~ ":" ~ to!string(line) ~ " (payload: " ~ to!string(value) ~ ")");
401           ok: ;
402         };
403     }
404 
405     /** Check that assignment from `rhs` is ok. */
406     void checkAssign(U, string file = __FILE__, int line = __LINE__)(U rhs)
407     {
408         if (rhs < min)
409             goto overflow;
410         if (rhs > max)
411             goto overflow;
412         goto ok;
413     overflow:
414         throw new BoundOverflowException("Overflow at " ~ file ~ ":" ~ to!string(line) ~ " (payload: " ~ to!string(rhs) ~ ")");
415     ok: ;
416     }
417 
418     auto opUnary(string op, string file = __FILE__, int line = __LINE__)()
419     {
420         static      if (op == "+")
421         {
422             return this;
423         }
424         else static if (op == "-")
425         {
426             Bound!(-cast(int)V.max,
427                    -cast(int)V.min) tmp = void; // TODO: Needs fix
428         }
429         mixin("tmp._value = " ~ op ~ "_value " ~ ";");
430         mixin(check());
431         return this;
432     }
433 
434     auto opBinary(string op, U,
435                   string file = __FILE__,
436                   int line = __LINE__)(U rhs)
437         if (haveCommonType!(V, U))
438     {
439         alias TU = CommonType!(V, U.type);
440         static if (is(U == Bound))
441         {
442             // do value range propagation
443             static      if (op == "+")
444             {
445                 enum min_ = min + U.min;
446                 enum max_ = max + U.max;
447             }
448             else static if (op == "-")
449             {
450                 enum min_ = min - U.max; // min + min(-U.max)
451                 enum max_ = max - U.min; // max + max(-U.max)
452             }
453             else static if (op == "*")
454             {
455                 import std.math: abs;
456                 static if (_value*rhs._value>= 0) // intuitive case
457                 {
458                     enum min_ = abs(min)*abs(U.min);
459                     enum max_ = abs(max)*abs(U.max);
460                 }
461                 else
462                 {
463                     enum min_ = -abs(max)*abs(U.max);
464                     enum max_ = -abs(min)*abs(U.min);
465                 }
466             }
467             /* else static if (op == "/") */
468             /* { */
469             /* } */
470             else static if (op == "^^")  // TODO: Verify this case for integers and floats
471             {
472                 import nxt.traits_ex: isEven;
473                 if (_value >= 0 ||
474                     (rhs._value >= 0 &&
475                      rhs._value.isEven)) // always positive if exponent is even
476                 {
477                     enum min_ = min^^U.min;
478                     enum max_ = max^^U.max;
479                 }
480                 else
481                 {
482                     /* TODO: What to do here? */
483                     enum min_ = max^^U.max;
484                     enum max_ = min^^U.min;
485                 }
486             }
487             else
488             {
489                 static assert(0, "Unsupported binary operator " + op);
490             }
491             alias TU_ = CommonType!(typeof(min_), typeof(max_));
492 
493             mixin("const result = _value " ~ op ~ "rhs;");
494 
495             /* static assert(0, min_.stringof ~ "," ~ */
496             /*               max_.stringof ~ "," ~ */
497             /*               typeof(result).stringof ~ "," ~ */
498             /*               TU_.stringof); */
499 
500             return bound!(min_, max_)(result);
501             // return Bound!(TU_, min_, max_)(result);
502         }
503         else
504         {
505             CommonType!(V, U) tmp = void;
506         }
507         mixin("const tmp = _value " ~ op ~ "rhs;");
508         mixin(check());
509         return this;
510     }
511 
512     private V _value;           /// Payload.
513 }
514 
515 /** Instantiate \c Bound from a single expression `expr`.
516  *
517  * Makes it easier to add free-contants to existing Bounded variables.
518  */
519 template bound(alias value)
520 if (isCTBound!value)
521 {
522     const bound = Bound!(PackedNumericType!value, value, value)(value);
523 }
524 
525 unittest
526 {
527     int x = 13;
528     static assert(!__traits(compiles, { auto y = bound!x; }));
529     static assert(is(typeof(bound!13) == const Bound!(ubyte, 13, 13)));
530     static assert(is(typeof(bound!13.0) == const Bound!(double, 13.0, 13.0)));
531     static assert(is(typeof(bound!13.0L) == const Bound!(real, 13.0L, 13.0L)));
532 }
533 
534 /** Instantiator for \c Bound.
535  *
536  * Bounds `low` and `high` infer type of internal _value.
537  * If `packed` optimize storage for compactness otherwise for speed.
538  *
539  * \see http://stackoverflow.com/questions/17502664/instantiator-function-for-bound-template-doesnt-compile
540  */
541 template bound(alias low,
542                alias high,
543                bool optional = false,
544                bool useExceptions = true,
545                bool packed = true,
546                bool signed = false)
547 if (isCTBound!low &&
548         isCTBound!high)
549 {
550     alias V = BoundsType!(low, high, packed, signed); // ValueType
551     alias C = CommonType!(typeof(low),
552                           typeof(high));
553 
554     auto bound()
555     {
556         return Bound!(V, low, high, optional, useExceptions, packed, signed)(V.init);
557     }
558     auto bound(V)(V value = V.init)
559     {
560         return Bound!(V, low, high, optional, useExceptions, packed, signed)(value);
561     }
562 }
563 
564 unittest
565 {
566     // static underflow
567     static assert(!__traits(compiles, { auto x = -1.bound!(0, 1); }));
568 
569     // dynamic underflow
570     int m1 = -1;
571     assertThrown(m1.bound!(0, 1));
572 
573     // dynamic overflows
574     assertThrown(2.bound!(0, 1));
575     assertThrown(255.bound!(0, 1));
576     assertThrown(256.bound!(0, 1));
577 
578     // dynamic assignment overflows
579     auto b1 = 1.bound!(0, 1);
580     assertThrown(b1 = 2);
581     assertThrown(b1 = -1);
582     assertThrown(b1 = 256);
583     assertThrown(b1 = -255);
584 
585     Bound!(int, int.min, int.max) a;
586 
587     a = int.max;
588     assert(a.value == int.max);
589 
590     Bound!(int, int.min, int.max) b;
591     b = int.min;
592     assert(b.value == int.min);
593 
594     a -= 5;
595     assert(a.value == int.max - 5); // ok
596     a += 5;
597 
598     /* assertThrown(a += 5); */
599 
600     auto x = bound!(0, 1)(1);
601     x += 1;
602 }
603 
604 unittest
605 {
606     /* TODO: static assert(is(typeof(bound!13 + bound!14) == const Bound!(ubyte, 27, 27))); */
607 }
608 
609 /** Return `x` with automatic packed saturation.
610  *
611  * If `packed` optimize storage for compactness otherwise for speed.
612  */
613 auto saturated(V,
614                bool optional = false,
615                bool packed = true)(V x) // TODO: inout may be irrelevant here
616 {
617     enum useExceptions = false;
618     return bound!(V.min, V.max, optional, useExceptions, packed)(x);
619 }
620 
621 /** Return `x` with automatic packed saturation.
622  *
623  * If `packed` optimize storage for compactness otherwise for speed.
624 */
625 auto optional(V, bool packed = true)(V x) // TODO: inout may be irrelevant here
626 {
627     return bound!(V.min, V.max, true, false, packed)(x);
628 }
629 
630 unittest
631 {
632     const sb127 = saturated!byte(127);
633     static assert(!__traits(compiles, { const sb128 = saturated!byte(128); }));
634     static assert(!__traits(compiles, { saturated!byte bb = 127; }));
635 }
636 
637 unittest
638 {
639     const sb127 = saturated!byte(127);
640     auto sh128 = saturated!short(128);
641     sh128 = sb127;
642     static assert(__traits(compiles, { sh128 = sb127; }));
643     static assert(!__traits(compiles, { sh127 = sb128; }));
644 }
645 
646 unittest
647 {
648     import std.meta: AliasSeq;
649     static saturatedTest(T)()
650     {
651         const shift = T.max;
652         auto x = saturated!T(shift);
653         static assert(x.sizeof == T.sizeof);
654         x -= shift; assert(x == T.min);
655         x += shift; assert(x == T.max);
656         // TODO: Make this work
657         // x -= shift + 1; assert(x == T.min);
658         // x += shift + 1; assert(x == T.max);
659     }
660 
661     foreach (T; AliasSeq!(ubyte, ushort, uint, ulong))
662     {
663         saturatedTest!T();
664     }
665 }
666 
667 /** Calculate Minimum.
668     TODO: variadic.
669 */
670 auto min(V1, alias low1, alias high1,
671          V2, alias low2, alias high2,
672          bool optional = false,
673          bool useExceptions = true,
674          bool packed = true,
675          bool signed = false)(Bound!(V1, low1, high1,
676                                      optional, useExceptions, packed, signed) a1,
677                               Bound!(V2, low2, high2,
678                                      optional, useExceptions, packed, signed) a2)
679 {
680     import std.algorithm: min;
681     enum lowMin = min(low1, low2);
682     enum highMin = min(high1, high2);
683     return (cast(BoundsType!(lowMin,
684                              highMin))min(a1.value,
685                                           a2.value)).bound!(lowMin,
686                                                             highMin);
687 }
688 
689 /** Calculate Maximum.
690     TODO: variadic.
691 */
692 auto max(V1, alias low1, alias high1,
693          V2, alias low2, alias high2,
694          bool optional = false,
695          bool useExceptions = true,
696          bool packed = true,
697          bool signed = false)(Bound!(V1, low1, high1,
698                                      optional, useExceptions, packed, signed) a1,
699                               Bound!(V2, low2, high2,
700                                      optional, useExceptions, packed, signed) a2)
701 {
702     import std.algorithm: max;
703     enum lowMax = max(low1, low2);
704     enum highMax = max(high1, high2);
705     return (cast(BoundsType!(lowMax,
706                              highMax))max(a1.value,
707                                           a2.value)).bound!(lowMax,
708                                                             highMax);
709 }
710 
711 unittest
712 {
713     const a = 11.bound!(0, 17);
714     const b = 11.bound!(5, 22);
715     const abMin = min(a, b);
716     static assert(is(typeof(abMin) == const Bound!(ubyte, 0, 17)));
717     const abMax = max(a, b);
718     static assert(is(typeof(abMax) == const Bound!(ubyte, 5, 22)));
719 }
720 
721 /** Calculate absolute value of `a`. */
722 auto abs(V,
723          alias low,
724          alias high,
725          bool optional = false,
726          bool useExceptions = true,
727          bool packed = true,
728          bool signed = false)(Bound!(V, low, high,
729                                      optional, useExceptions, packed, signed) a)
730 {
731     static if (low >= 0 && high >= 0) // all positive
732     {
733         enum low_ = low;
734         enum high_ = high;
735     }
736     else static if (low < 0 && high < 0) // all negative
737     {
738         enum low_ = -high;
739         enum high_ = -low;
740     }
741     else static if (low < 0 && high >= 0) // negative and positive
742     {
743         import std.algorithm: max;
744         enum low_ = 0;
745         enum high_ = max(-low, high);
746     }
747     else
748     {
749         static assert("This shouldn't happen!");
750     }
751     import std.math: abs;
752     return Bound!(BoundsType!(low_, high_),
753                   low_, high_,
754                   optional, useExceptions, packed, signed)(a.value.abs - low_);
755 }
756 
757 unittest
758 {
759     static assert(is(typeof(abs(0.bound!(-3, +3))) == Bound!(ubyte, 0, 3)));
760     static assert(is(typeof(abs(0.bound!(-3, -1))) == Bound!(ubyte, 1, 3)));
761     static assert(is(typeof(abs(0.bound!(-3, +0))) == Bound!(ubyte, 0, 3)));
762     static assert(is(typeof(abs(0.bound!(+0, +3))) == Bound!(ubyte, 0, 3)));
763     static assert(is(typeof(abs(0.bound!(+1, +3))) == Bound!(ubyte, 1, 3)));
764     static assert(is(typeof(abs(0.bound!(-255, 255))) == Bound!(ubyte, 0, 255)));
765     static assert(is(typeof(abs(0.bound!(-256, 255))) == Bound!(ushort, 0, 256)));
766     static assert(is(typeof(abs(0.bound!(-255, 256))) == Bound!(ushort, 0, 256)));
767     static assert(is(typeof(abs(10_000.bound!(10_000, 10_000+255))) == Bound!(ubyte, 10_000, 10_000+255)));
768 }
769 
770 unittest
771 {
772     auto x01 = 0.bound!(0, 1);
773     auto x02 = 0.bound!(0, 2);
774     static assert( __traits(compiles, { x02 = x01; })); // ok within range
775     static assert(!__traits(compiles, { x01 = x02; })); // should fail
776 }
777 
778 /** TODO: Can D do better than C++ here?
779     Does this automatically deduce to CommonType and if so do we need to declare it?
780     Or does it suffice to constructors?
781  */
782 version(none)
783 {
784     auto doIt(ubyte x)
785     {
786         if (x >= 0)
787             return x.bound!(0, 2);
788         else
789             return x.bound!(0, 1);
790     }
791 
792     unittest
793     {
794         auto x = 0.doIt;
795     }
796 }