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 }