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