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 }