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