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             {
187                 alias BoundsType = CommonType!(LowType, HighType);
188             }
189         }
190         else                    // positive
191         {
192             static if (packed)
193             {
194                 static      if (span <= 0xff)               { alias BoundsType = ubyte; }
195                 else static if (span <= 0xffff)             { alias BoundsType = ushort; }
196                 else static if (span <= 0xffffffff)         { alias BoundsType = uint; }
197                 else static if (span <= 0xffffffffffffffff) { alias BoundsType = ulong; }
198                 else { alias BoundsType = CommonType!(LowType, HighType); }
199             }
200             else
201             {
202                 alias BoundsType = CommonType!(LowType, HighType);
203             }
204         }
205     }
206     else static if (isFloatingPoint!LowType &&
207                     isFloatingPoint!HighType)
208     {
209         alias BoundsType = CommonType!(LowType, HighType);
210     }
211     else static if (isSomeChar!LowType &&
212                     isSomeChar!HighType)
213     {
214         alias BoundsType = CommonType!(LowType, HighType);
215     }
216     else static if (isBoolean!LowType &&
217                     isBoolean!HighType)
218     {
219         alias BoundsType = CommonType!(LowType, HighType);
220     }
221     else
222     {
223         static assert(0, "Cannot construct a bound using types " ~ LowType.stringof ~ " and " ~ HighType.stringof);
224     }
225 }
226 
227 unittest
228 {
229     assertThrown(('a'.bound!(false, true)));
230     assertThrown((false.bound!('a', 'z')));
231     //wln(false.bound!('a', 'z')); // TODO Should this give compile-time error?
232 
233     static assert(!__traits(compiles, { alias IBT = BoundsType!(0, 0); }));  // disallow
234     static assert(!__traits(compiles, { alias IBT = BoundsType!(1, 0); })); // disallow
235 
236     static assert(is(typeof(false.bound!(false, true)) == Bound!(bool, false, true)));
237     static assert(is(typeof('p'.bound!('a', 'z')) == Bound!(char, 'a', 'z')));
238 
239     // low < 0
240     static assert(is(BoundsType!(-1, 0, true, true) == byte));
241     static assert(is(BoundsType!(-1, 0, true, false) == ubyte));
242     static assert(is(BoundsType!(-0xff, 0, true, false) == ubyte));
243     static assert(is(BoundsType!(-0xff, 1, true, false) == ushort));
244 
245     static assert(is(BoundsType!(byte.min, byte.max, true, true) == byte));
246     static assert(is(BoundsType!(byte.min, byte.max + 1, true, true) == short));
247 
248     static assert(is(BoundsType!(short.min, short.max, true, true) == short));
249     static assert(is(BoundsType!(short.min, short.max + 1, true, true) == int));
250 
251     // low == 0
252     static assert(is(BoundsType!(0, 0x1) == ubyte));
253     static assert(is(BoundsType!(ubyte.min, ubyte.max) == ubyte));
254 
255     static assert(is(BoundsType!(ubyte.min, ubyte.max + 1) == ushort));
256     static assert(is(BoundsType!(ushort.min, ushort.max) == ushort));
257 
258     static assert(is(BoundsType!(ushort.min, ushort.max + 1) == uint));
259     static assert(is(BoundsType!(uint.min, uint.max) == uint));
260 
261     static assert(is(BoundsType!(uint.min, uint.max + 1UL) == ulong));
262     static assert(is(BoundsType!(ulong.min, ulong.max) == ulong));
263 
264     // low > 0
265     static assert(is(BoundsType!(ubyte.max, ubyte.max + ubyte.max) == ubyte));
266     static assert(is(BoundsType!(ubyte.max, ubyte.max + 0x100) == ushort));
267     static assert(is(BoundsType!(uint.max + 1UL, uint.max + 1UL + ubyte.max) == ubyte));
268     static assert(!is(BoundsType!(uint.max + 1UL, uint.max + 1UL + ubyte.max + 1) == ubyte));
269 
270     // floating point
271     static assert(is(BoundsType!(0.0, 10.0) == double));
272 }
273 
274 /** Value of type `V` bound inside inclusive range [`low`, `high`].
275 
276     If `optional` is `true`, this stores one extra undefined state (similar to
277     Haskell's `Maybe`).
278 
279     If `useExceptions` is true range errors will throw a `BoundOverflowException`,
280     otherwise truncation plus warnings will issued.
281 */
282 struct Bound(V,
283              alias low,
284              alias high,
285              bool optional = false,
286              bool useExceptions = true,
287              bool packed = true,
288              bool signed = false)
289 if (isBoundable!V)
290 {
291     /* Requirements */
292     static assert(low <= high,
293                   "Requirement not fulfilled: low < high, low = " ~
294                   to!string(low) ~ " and high = " ~ to!string(high));
295     static if (optional)
296     {
297         static assert(high + 1 == V.max,
298                       "high + 1 cannot equal V.max");
299     }
300 
301     /** Get low inclusive bound. */
302     static auto min() @property @safe pure nothrow { return low; }
303 
304     /** Get high inclusive bound. */
305     static auto max() @property @safe pure nothrow { return optional ? high - 1 : high; }
306 
307     static if (isIntegral!V && low >= 0)
308     {
309         size_t opCast(U : size_t)() const { return this._value; } // for IndexedBy support
310     }
311 
312     /** Construct from unbounded value `rhs`. */
313     this(U, string file = __FILE__, int line = __LINE__)(U rhs)
314         if (isBoundable!(U))
315     {
316         checkAssign!(U, file, line)(rhs);
317         this._value = cast(V)(rhs - low);
318     }
319     /** Assigne from unbounded value `rhs`. */
320     auto opAssign(U, string file = __FILE__, int line = __LINE__)(U rhs)
321         if (isBoundable!(U))
322     {
323         checkAssign!(U, file, line)(rhs);
324         _value = rhs - low;
325         return this;
326     }
327 
328     bool opEquals(U)(U rhs) const
329         if (is(typeof({ auto _ = V.init == U.init; })))
330     {
331         return value() == rhs;
332     }
333 
334     /** Construct from `Bound` value `rhs`. */
335     this(U,
336          alias low_,
337          alias high_)(Bound!(U, low_, high_,
338                              optional, useExceptions, packed, signed) rhs)
339         if (low <= low_ &&
340             high_ <= high)
341     {
342         // verified at compile-time
343         this._value = rhs._value + (high - high_);
344     }
345 
346     /** Assign from `Bound` value `rhs`. */
347     auto opAssign(U,
348                   alias low_,
349                   alias high_)(Bound!(U, low_, high_,
350                                       optional, useExceptions, packed, signed) rhs)
351         if (low <= low_ &&
352             high_ <= high &&
353             haveCommonType!(V, U))
354     {
355         // verified at compile-time
356         this._value = rhs._value + (high - high_);
357         return this;
358     }
359 
360     auto opOpAssign(string op, U, string file = __FILE__, int line = __LINE__)(U rhs)
361         if (haveCommonType!(V, U))
362     {
363         CommonType!(V, U) tmp = void;
364         mixin("tmp = _value " ~ op ~ "rhs;");
365         mixin(check());
366         _value = cast(V)tmp;
367         return this;
368     }
369 
370     @property auto value() inout
371     {
372         return _value + this.min;
373     }
374 
375     @property void toString(scope void delegate(scope const(char)[]) @safe sink) const
376     {
377         import std.format : formattedWrite;
378         sink.formattedWrite!"%s ∈ [%s, %s] ⟒ %s"(this.value,
379                                                  min,
380                                                  max,
381                                                  V.stringof);
382     }
383 
384     /** Check if this value is defined. */
385     @property bool isDefined() @safe const pure nothrow @nogc
386     {
387         return optional ? this.value != V.max : true;
388     }
389 
390     /** Check that last operation was a success. */
391     static string check() @trusted pure @nogc
392     {
393         return q{
394             asm { jo overflow; }
395             if (value < min) goto overflow;
396             if (value > max) goto overflow;
397             goto ok;
398           // underflow:
399           //   immutable uMsg = "Underflow at " ~ file ~ ":" ~ to!string(line) ~ " (payload: " ~ to!string(value) ~ ")";
400           //   if (useExceptions) {
401           //       throw new BoundUnderflowException(uMsg);
402           //   } else {
403           //       wln(uMsg);
404           //   }
405           overflow:
406             throw new BoundOverflowException("Overflow at " ~ file ~ ":" ~ to!string(line) ~ " (payload: " ~ to!string(value) ~ ")");
407           ok: ;
408         };
409     }
410 
411     /** Check that assignment from `rhs` is ok. */
412     void checkAssign(U, string file = __FILE__, int line = __LINE__)(U rhs)
413     {
414         if (rhs < min) goto overflow;
415         if (rhs > max) goto overflow;
416         goto ok;
417     overflow:
418         throw new BoundOverflowException("Overflow at " ~ file ~ ":" ~ to!string(line) ~ " (payload: " ~ to!string(rhs) ~ ")");
419     ok: ;
420     }
421 
422     auto opUnary(string op, string file = __FILE__, int line = __LINE__)()
423     {
424         static      if (op == "+")
425         {
426             return this;
427         }
428         else static if (op == "-")
429         {
430             Bound!(-cast(int)V.max,
431                    -cast(int)V.min) tmp = void; // TODO Needs fix
432         }
433         mixin("tmp._value = " ~ op ~ "_value " ~ ";");
434         mixin(check());
435         return this;
436     }
437 
438     auto opBinary(string op, U,
439                   string file = __FILE__,
440                   int line = __LINE__)(U rhs)
441         if (haveCommonType!(V, U))
442     {
443         alias TU = CommonType!(V, U.type);
444         static if (is(U == Bound))
445         {
446             // do value range propagation
447             static      if (op == "+")
448             {
449                 enum min_ = min + U.min;
450                 enum max_ = max + U.max;
451             }
452             else static if (op == "-")
453             {
454                 enum min_ = min - U.max; // min + min(-U.max)
455                 enum max_ = max - U.min; // max + max(-U.max)
456             }
457             else static if (op == "*")
458             {
459                 import std.math: abs;
460                 static if (_value*rhs._value>= 0) // intuitive case
461                 {
462                     enum min_ = abs(min)*abs(U.min);
463                     enum max_ = abs(max)*abs(U.max);
464                 }
465                 else
466                 {
467                     enum min_ = -abs(max)*abs(U.max);
468                     enum max_ = -abs(min)*abs(U.min);
469                 }
470             }
471             /* else static if (op == "/") */
472             /* { */
473             /* } */
474             else static if (op == "^^")  // TODO Verify this case for integers and floats
475             {
476                 import nxt.traits_ex: isEven;
477                 if (_value >= 0 ||
478                     (rhs._value >= 0 &&
479                      rhs._value.isEven)) // always positive if exponent is even
480                 {
481                     enum min_ = min^^U.min;
482                     enum max_ = max^^U.max;
483                 }
484                 else
485                 {
486                     /* TODO What to do here? */
487                     enum min_ = max^^U.max;
488                     enum max_ = min^^U.min;
489                 }
490             }
491             else
492             {
493                 static assert(0, "Unsupported binary operator " + op);
494             }
495             alias TU_ = CommonType!(typeof(min_), typeof(max_));
496 
497             mixin("const result = _value " ~ op ~ "rhs;");
498 
499             /* static assert(0, min_.stringof ~ "," ~ */
500             /*               max_.stringof ~ "," ~ */
501             /*               typeof(result).stringof ~ "," ~ */
502             /*               TU_.stringof); */
503 
504             return bound!(min_, max_)(result);
505             // return Bound!(TU_, min_, max_)(result);
506         }
507         else
508         {
509             CommonType!(V, U) tmp = void;
510         }
511         mixin("const tmp = _value " ~ op ~ "rhs;");
512         mixin(check());
513         return this;
514     }
515 
516     private V _value;           /// Payload.
517 }
518 
519 /** Instantiate \c Bound from a single expression `expr`.
520  *
521  * Makes it easier to add free-contants to existing Bounded variables.
522  */
523 template bound(alias value)
524 if (isCTBound!value)
525 {
526     const bound = Bound!(PackedNumericType!value, value, value)(value);
527 }
528 
529 unittest
530 {
531     int x = 13;
532     static assert(!__traits(compiles, { auto y = bound!x; }));
533     static assert(is(typeof(bound!13) == const Bound!(ubyte, 13, 13)));
534     static assert(is(typeof(bound!13.0) == const Bound!(double, 13.0, 13.0)));
535     static assert(is(typeof(bound!13.0L) == const Bound!(real, 13.0L, 13.0L)));
536 }
537 
538 /** Instantiator for \c Bound.
539  *
540  * Bounds `low` and `high` infer type of internal _value.
541  * If `packed` optimize storage for compactness otherwise for speed.
542  *
543  * \see http://stackoverflow.com/questions/17502664/instantiator-function-for-bound-template-doesnt-compile
544  */
545 template bound(alias low,
546                alias high,
547                bool optional = false,
548                bool useExceptions = true,
549                bool packed = true,
550                bool signed = false)
551 if (isCTBound!low &&
552         isCTBound!high)
553 {
554     alias V = BoundsType!(low, high, packed, signed); // ValueType
555     alias C = CommonType!(typeof(low),
556                           typeof(high));
557 
558     auto bound()
559     {
560         return Bound!(V, low, high, optional, useExceptions, packed, signed)(V.init);
561     }
562     auto bound(V)(V value = V.init)
563     {
564         return Bound!(V, low, high, optional, useExceptions, packed, signed)(value);
565     }
566 }
567 
568 unittest
569 {
570     // static underflow
571     static assert(!__traits(compiles, { auto x = -1.bound!(0, 1); }));
572 
573     // dynamic underflow
574     int m1 = -1;
575     assertThrown(m1.bound!(0, 1));
576 
577     // dynamic overflows
578     assertThrown(2.bound!(0, 1));
579     assertThrown(255.bound!(0, 1));
580     assertThrown(256.bound!(0, 1));
581 
582     // dynamic assignment overflows
583     auto b1 = 1.bound!(0, 1);
584     assertThrown(b1 = 2);
585     assertThrown(b1 = -1);
586     assertThrown(b1 = 256);
587     assertThrown(b1 = -255);
588 
589     Bound!(int, int.min, int.max) a;
590 
591     a = int.max;
592     assert(a.value == int.max);
593 
594     Bound!(int, int.min, int.max) b;
595     b = int.min;
596     assert(b.value == int.min);
597 
598     a -= 5;
599     assert(a.value == int.max - 5); // ok
600     a += 5;
601 
602     /* assertThrown(a += 5); */
603 
604     auto x = bound!(0, 1)(1);
605     x += 1;
606 }
607 
608 unittest
609 {
610     /* TODO static assert(is(typeof(bound!13 + bound!14) == const Bound!(ubyte, 27, 27))); */
611 }
612 
613 /** Return `x` with automatic packed saturation.
614  *
615  * If `packed` optimize storage for compactness otherwise for speed.
616  */
617 auto saturated(V,
618                bool optional = false,
619                bool packed = true)(V x) // TODO inout may be irrelevant here
620 {
621     enum useExceptions = false;
622     return bound!(V.min, V.max, optional, useExceptions, packed)(x);
623 }
624 
625 /** Return `x` with automatic packed saturation.
626  *
627  * If `packed` optimize storage for compactness otherwise for speed.
628 */
629 auto optional(V, bool packed = true)(V x) // TODO inout may be irrelevant here
630 {
631     return bound!(V.min, V.max, true, false, packed)(x);
632 }
633 
634 unittest
635 {
636     const sb127 = saturated!byte(127);
637     static assert(!__traits(compiles, { const sb128 = saturated!byte(128); }));
638     static assert(!__traits(compiles, { saturated!byte bb = 127; }));
639 }
640 
641 unittest
642 {
643     const sb127 = saturated!byte(127);
644     auto sh128 = saturated!short(128);
645     sh128 = sb127;
646     static assert(__traits(compiles, { sh128 = sb127; }));
647     static assert(!__traits(compiles, { sh127 = sb128; }));
648 }
649 
650 unittest
651 {
652     import std.meta: AliasSeq;
653     static saturatedTest(T)()
654     {
655         const shift = T.max;
656         auto x = saturated!T(shift);
657         static assert(x.sizeof == T.sizeof);
658         x -= shift; assert(x == T.min);
659         x += shift; assert(x == T.max);
660         // TODO Make this work
661         // x -= shift + 1; assert(x == T.min);
662         // x += shift + 1; assert(x == T.max);
663     }
664 
665     foreach (T; AliasSeq!(ubyte, ushort, uint, ulong))
666     {
667         saturatedTest!T();
668     }
669 }
670 
671 /** Calculate Minimum.
672     TODO variadic.
673 */
674 auto min(V1, alias low1, alias high1,
675          V2, alias low2, alias high2,
676          bool optional = false,
677          bool useExceptions = true,
678          bool packed = true,
679          bool signed = false)(Bound!(V1, low1, high1,
680                                      optional, useExceptions, packed, signed) a1,
681                               Bound!(V2, low2, high2,
682                                      optional, useExceptions, packed, signed) a2)
683 {
684     import std.algorithm: min;
685     enum lowMin = min(low1, low2);
686     enum highMin = min(high1, high2);
687     return (cast(BoundsType!(lowMin,
688                              highMin))min(a1.value,
689                                           a2.value)).bound!(lowMin,
690                                                             highMin);
691 }
692 
693 /** Calculate Maximum.
694     TODO variadic.
695 */
696 auto max(V1, alias low1, alias high1,
697          V2, alias low2, alias high2,
698          bool optional = false,
699          bool useExceptions = true,
700          bool packed = true,
701          bool signed = false)(Bound!(V1, low1, high1,
702                                      optional, useExceptions, packed, signed) a1,
703                               Bound!(V2, low2, high2,
704                                      optional, useExceptions, packed, signed) a2)
705 {
706     import std.algorithm: max;
707     enum lowMax = max(low1, low2);
708     enum highMax = max(high1, high2);
709     return (cast(BoundsType!(lowMax,
710                              highMax))max(a1.value,
711                                           a2.value)).bound!(lowMax,
712                                                             highMax);
713 }
714 
715 unittest
716 {
717     const a = 11.bound!(0, 17);
718     const b = 11.bound!(5, 22);
719     const abMin = min(a, b);
720     static assert(is(typeof(abMin) == const Bound!(ubyte, 0, 17)));
721     const abMax = max(a, b);
722     static assert(is(typeof(abMax) == const Bound!(ubyte, 5, 22)));
723 }
724 
725 /** Calculate absolute value of `a`. */
726 auto abs(V,
727          alias low,
728          alias high,
729          bool optional = false,
730          bool useExceptions = true,
731          bool packed = true,
732          bool signed = false)(Bound!(V, low, high,
733                                      optional, useExceptions, packed, signed) a)
734 {
735     static if (low >= 0 && high >= 0) // all positive
736     {
737         enum low_ = low;
738         enum high_ = high;
739     }
740     else static if (low < 0 && high < 0) // all negative
741     {
742         enum low_ = -high;
743         enum high_ = -low;
744     }
745     else static if (low < 0 && high >= 0) // negative and positive
746     {
747         import std.algorithm: max;
748         enum low_ = 0;
749         enum high_ = max(-low, high);
750     }
751     else
752     {
753         static assert("This shouldn't happen!");
754     }
755     import std.math: abs;
756     return Bound!(BoundsType!(low_, high_),
757                   low_, high_,
758                   optional, useExceptions, packed, signed)(a.value.abs - low_);
759 }
760 
761 unittest
762 {
763     static assert(is(typeof(abs(0.bound!(-3, +3))) == Bound!(ubyte, 0, 3)));
764     static assert(is(typeof(abs(0.bound!(-3, -1))) == Bound!(ubyte, 1, 3)));
765     static assert(is(typeof(abs(0.bound!(-3, +0))) == Bound!(ubyte, 0, 3)));
766     static assert(is(typeof(abs(0.bound!(+0, +3))) == Bound!(ubyte, 0, 3)));
767     static assert(is(typeof(abs(0.bound!(+1, +3))) == Bound!(ubyte, 1, 3)));
768     static assert(is(typeof(abs(0.bound!(-255, 255))) == Bound!(ubyte, 0, 255)));
769     static assert(is(typeof(abs(0.bound!(-256, 255))) == Bound!(ushort, 0, 256)));
770     static assert(is(typeof(abs(0.bound!(-255, 256))) == Bound!(ushort, 0, 256)));
771     static assert(is(typeof(abs(10_000.bound!(10_000, 10_000+255))) == Bound!(ubyte, 10_000, 10_000+255)));
772 }
773 
774 unittest
775 {
776     auto x01 = 0.bound!(0, 1);
777     auto x02 = 0.bound!(0, 2);
778     static assert( __traits(compiles, { x02 = x01; })); // ok within range
779     static assert(!__traits(compiles, { x01 = x02; })); // should fail
780 }
781 
782 /** TODO Can D do better than C++ here?
783     Does this automatically deduce to CommonType and if so do we need to declare it?
784     Or does it suffice to constructors?
785  */
786 version(none)
787 {
788     auto doIt(ubyte x)
789     {
790         if (x >= 0)
791         {
792             return x.bound!(0, 2);
793         }
794         else
795         {
796             return x.bound!(0, 1);
797         }
798     }
799 
800     unittest
801     {
802         auto x = 0.doIt;
803     }
804 }