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 }