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