1 /** 2 Checked integer arithmetic operations, functions, and types with improved handling of errors and corner cases compared 3 to the basic integral types. 4 5 $(B Note:) Normally this module should not be imported directly. Instead, import one of 6 $(LINK2 ./throws.html, `checkedint.throws`), $(LINK2 ./asserts.html, `checkedint.asserts`), or 7 $(LINK2 ./sticky.html, `checkedint.sticky`), depending on which error signalling policy is needed. (See below.) 8 9 $(BIG $(B Problems solved by `checkedint`)) $(BR) 10 As in many other programming languages (C, C++, Java, etc.) D's basic integral types (such as `int` or `ulong`) are 11 surprisingly difficult to use correctly in the general case, due to variuos departures from the behaviour of ideal 12 mathematical integers: 13 $(UL 14 $(LI Overflow silently wraps around: `assert(uint.max + 1 == 0);`) 15 $(LI Mixed signed/unsigned comparisons often give the wrong result: `assert(-1 > 1u);`) 16 $(LI Mixed signed/unsigned arithmetic operations can also give the wrong result.) 17 $(LI Integer division by zero crashes the program with a mis-named and uncatchable `Floating Point Exception` 18 (FPE).) 19 $(LI `int.min / -1` and `int.min % -1` may also crash with an FPE, even though the latter should simply yield `0`.) 20 $(LI If `x` is any integer value, and `y` is any negative integer value, `x ^^ y` will crash with an FPE.) 21 $(LI No bounds checking is done when casting from one integer type to another.) 22 $(LI The result of the bitshift operations (`<<`, `>>`, `>>>`) is formally undefined if the shift size is less than 23 zero or greater than `(8 * N.sizeof) - 1`.) 24 ) 25 The `checkedint` package offers solutions to all of these issues and more. 26 27 $(BIG $(B `SafeInt` versus `SmartInt`)) $(BR) 28 Two different approaches are available: 29 $(UL 30 $(LI $(LINK2 ./package.html#SmartInt, `SmartInt`) and $(LINK2 ./package.html#smartOp, `smartOp`) strive to actually 31 give the mathematically correct answer whenever possible, rather than just signaling an error.) 32 $(LI $(LINK2 ./package.html#SafeInt, `SafeInt`) and $(LINK2 ./package.html#safeOp, `safeOp`) strive to match the 33 behaviour of the basic integral types exactly, $(B except) that where the behaviour of the basic type is 34 wrong, or very unintuitive, an error is signaled instead.) 35 ) 36 There is no meaningful performance difference between `SafeInt` and `SmartInt`. For general use, choosing `SmartInt` 37 simplifies code and maximizes the range of inputs accepted. 38 39 `SafeInt` is intended mainly as a debugging tool, to help identify problems in code that must also work correctly with 40 the basic integral types. The $(LINK2 ./package.html#DebugInt, `DebugInt`) `template` `alias` makes it simple to use 41 of `SafeInt` in debug builds, and raw basic types in release builds. 42 43 $(TABLE 44 $(TR $(TD) $(TH `int` (basic type)) $(TH `SafeInt!int`) $(TH `SmartInt!int`)) 45 $(TR $(TH `int.max + 1`) $(TD `int.min`) $(TD `raise(IntFlag.over)`) $(TD `raise(IntFlag.over)`)) 46 $(TR $(TH `-1 > 1u`) $(TD `true`) $(TD compile-time error) $(TD `false`)) 47 $(TR $(TH `-1 - 2u`) $(TD `4294967293`) $(TD compile-time error) $(TD `-3`)) 48 $(TR $(TH `1 / 0`) $(TD crash by FPE) $(TD `raise(IntFlag.div0)`) $(TD `raise(IntFlag.div0)`)) 49 $(TR $(TH `int.min % -1`) $(TD crash by FPE) $(TD `raise(IntFlag.posOver)`) $(TD `0`)) 50 $(TR $(TH `-1 ^^ -7`) $(TD crash by FPE) $(TD `raise(IntFlag.undef)`) $(TD `-1`)) 51 $(TR $(TH `cast(uint)-1`) $(TD `4294967295`) $(TD compile-time error) $(TD `raise(IntFlag.negOver)`)) 52 $(TR $(TH `-1 >> 100`) $(TD undefined) $(TD `raise(IntFlag.undef)`) $(TD `-1`)) 53 ) 54 55 $(BIG $(B Error Signaling)) $(BR) 56 Some types of problem are signaled by a compile-time error, others at runtime. Runtime signaling is done through 57 $(LINK2 ./flags.html, `checkedint.flags`). Three different runtime signalling policies are available: 58 $(UL 59 $(LI With `IntFlagPolicy.throws`, a $(LINK2 ./flags.html#CheckedIntException, `CheckedIntException`) is thrown. 60 These are normal exceptions; not FPEs. As such, they can be caught and include a stack trace.) 61 $(LI With `IntFlagPolicy.asserts`, an assertion failure will be triggered. This policy is compatible with 62 `pure nothrow @nogc` code, but will crash the program in the event of a runtime integer math error.) 63 $(LI Alternatively, `IntFlagPolicy.sticky` can be selected so that a thread-local sticky flag is set when an 64 operation fails. This allows `checkedint` to be used from `nothrow` and `@nogc` (but not `pure`) code without 65 crashing the program, but requires the API user to manually insert checks of `IntFlags.local`.) 66 ) 67 In normal code, there is no performance penalty for allowing `checkedint` to `throw`. Doing so is highly recommended 68 because this makes it easier to use correctly, and yields more precise error messages when something goes wrong. 69 70 $(BIG $(B Generic Code)) $(BR) 71 The $(LINK2 ./traits.html, `checkedint.traits`) module provides `checkedint`-aware versions of various numerical type 72 traits from `std.traits`, such as `Signed`, `isSigned` and `isIntegral`. This allows writing generic algorithms that 73 work with any of `SmartInt`, `SafeInt`, and the built-in numeric types such as `uint` and `long`. 74 75 Also of note is the $(LINK2 ./package.html#idx, `idx()`) function, which concisely and safely casts from any integral type 76 (built-in, `SmartInt`, or `SafeInt`) to either `size_t` or `ptrdiff_t` for easy array indexing. 77 78 $(BIG $(B Performance)) $(BR) 79 Replacing all basic integer types with `SmartInt` or `SafeInt` will slow down exectuion somewhat. How much depends on 80 many factors, but for most code following a few simple rules should keep the penalty low: 81 $(OL 82 $(LI Build with $(LINK2 http://dlang.org/dmd-windows.html#switch-inline, $(B $(RED `-inline`))) and 83 $(LINK2 http://dlang.org/dmd-windows.html#switch-O, $(B `-O`)) (DMD) or $(B `-O3`) (GDC and LDC). This by 84 itself can improve the performance of `checkedint` by around $(B 1,000%).) 85 $(LI With GDC or LDC, the performance hit in code that is bottlenecked by integer math will probably be between 30% 86 and 100%. The performance hit may be considerably larger with DMD, due to the weakness of the inliner.) 87 $(LI `checkedint` can't slow down code where it's not used! For more speed, switch to 88 $(LINK2 ./package.html#DebugInt, `DebugInt`) for the hottest code in the program (like inner loops) before 89 giving up on `checkedint` entirely.) 90 ) 91 The above guidelines should be more than sufficient for most programs. But, some micro-optimization are possible as 92 well, if needed: 93 $(UL 94 $(LI Always use $(LINK2 ./package.html#smartOp.mulPow2, `mulPow2()`), 95 $(LINK2 ./package.html#smartOp.divPow2, `divPow2()`), and $(LINK2 ./package.html#smartOp.modPow2, `modPow2()`) 96 whenever they can naturally express the intent - they're faster than a regular `/`, `%`, or `pow()`.) 97 $(LI Unsigned types are a little bit faster than signed types, assuming negative values aren't needed.) 98 $(LI Although they are perfectly safe with `checkedint`, mixed signed/unsigned operations are a little bit slower 99 than same-signedness ones.) 100 $(LI The assignment operators (`++` or `+=`, for example) should never be slower than the equivalent two operation 101 sequence, and are sometimes a little bit faster.) 102 ) 103 104 References: $(LINK2 https://dlang.org/phobos/core_checkedint.html, core._checkedint) 105 106 Copyright: Copyright Thomas Stuart Bockman 2015 107 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 108 Authors: Thomas Stuart Bockman 109 **/ 110 module checkedint; 111 import checkedint.flags; 112 113 static assert(__VERSION__ >= 2071); 114 115 import core.bitop, core.checkedint, std.algorithm, std.format, std.meta, future.traits0, std.typecons; 116 static import std.math; 117 118 /+pragma(inline, true) 119 {+/ 120 // smart ///////////////////////////////////////////////// 121 /** 122 Wrapper for any basic integral type `N` that uses the checked operations from 123 $(LINK2 ./package.html#smartOp, `smartOp`) and bounds checks assignments with 124 $(LINK2 ./package.html#to, `checkedint.to()`). 125 126 $(UL 127 $(LI `policy` controls the error signalling policy (see `checkedint.flags`).) 128 $(LI `bitOps` may be set to `No.bitOps` if desired, to turn bitwise operations on this type into a 129 compile-time error.) 130 ) 131 **/ 132 struct SmartInt(N, IntFlagPolicy _policy, Flag!"bitOps" bitOps = Yes.bitOps) 133 if (isIntegral!N && isUnqual!N) 134 { 135 /// The error signalling policy used by this `SmartInt` type. 136 enum IntFlagPolicy policy = _policy; 137 138 static if (bitOps) 139 { 140 /** 141 The basic scalar value of this `SmartInt`. Accessing this directly may be useful for: 142 $(UL 143 $(LI Intentionally doing modular (unchecked) arithmetic, or) 144 $(LI Interacting with APIs that are not `checkedint` aware.) 145 ) 146 **/ 147 N bscal; 148 /// 149 unittest 150 { 151 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 152 153 SmartInt!uint n; 154 static assert(is(typeof(n.bscal) == uint)); 155 156 n = 7; 157 assert(n.bscal == 7); 158 159 n.bscal -= 8; 160 assert(n == uint.max); 161 } 162 163 /// Get a view of this `SmartInt` that allows bitwise operations. 164 @property ref inout(SmartInt!(N, policy, Yes.bitOps)) bits() return inout pure @safe nothrow @nogc 165 { 166 return this; 167 } 168 /// 169 unittest 170 { 171 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 172 173 SmartInt!(int, No.bitOps) n = 1; 174 static assert(!__traits(compiles, n << 2)); 175 assert(n.bits << 2 == 4); 176 } 177 } 178 else 179 { 180 @property ref inout(N) bscal() return inout pure @safe nothrow @nogc 181 { 182 return bits.bscal; 183 } 184 SmartInt!(N, policy, Yes.bitOps) bits; 185 } 186 187 /// The most negative possible value of this `SmartInt` type. 188 enum SmartInt!(N, policy, bitOps) min = typeof(this)(trueMin!N); 189 /// 190 unittest 191 { 192 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 193 194 assert(SmartInt!(int).min == int.min); 195 assert(SmartInt!(uint).min == uint.min); 196 } 197 198 /// The most positive possible value of this `SmartInt` type. 199 enum SmartInt!(N, policy, bitOps) max = typeof(this)(trueMax!N); 200 /// 201 unittest 202 { 203 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws; 204 205 assert(SmartInt!(int).max == int.max); 206 assert(SmartInt!(uint).max == uint.max); 207 } 208 209 // Construction, assignment, and casting ///////////////////////////////////////////////// 210 /** 211 Assign the value of `that` to this `SmartInt` instance. 212 213 $(LINK2 ./package.html#to, `checkedint.to()`) is used to verify `that >= N.min && that <= N.max`. If not, an 214 $(LINK2 ./flags.html, `IntFlag`) will be raised. 215 **/ 216 this(M)(const M that) @safe 217 if (isCheckedInt!M || isScalarType!M) 218 { 219 this.bscal = to!(N, policy)(that); 220 } 221 /// ditto 222 ref typeof(this) opAssign(M)(const M that) @safe 223 if (isCheckedInt!M || isScalarType!M) 224 { 225 this.bscal = to!(N, policy)(that); 226 return this; 227 } 228 /// 229 unittest 230 { 231 import checkedint.sticky : SmartInt; // use IntFlagPolicy.sticky 232 233 // Any basic scalar or checkedint *type* is accepted... 234 SmartInt!int n = 0; 235 n = cast(ulong)0; 236 n = cast(dchar)0; 237 n = cast(byte)0; 238 n = cast(real)0; 239 assert(!IntFlags.local); 240 241 // ...but not any *value*. 242 n = uint.max; 243 n = long.min; 244 n = real.nan; 245 assert(IntFlags.local.clear() == (IntFlag.posOver | IntFlag.negOver | IntFlag.undef)); 246 } 247 248 /** 249 Convert this value to floating-point. This always succeeds, although some loss of precision may 250 occur if M.sizeof <= N.sizeof. 251 **/ 252 M opCast(M)() const pure @safe nothrow @nogc 253 if (isFloatingPoint!M) 254 { 255 return cast(M)bscal; 256 } 257 /// 258 unittest 259 { 260 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 261 262 SmartInt!int n = 92; 263 auto f = cast(double)n; 264 static assert(is(typeof(f) == double)); 265 assert(f == 92.0); 266 } 267 268 /// `this != 0` 269 M opCast(M)() const pure @safe nothrow @nogc 270 if (is(M == bool)) 271 { 272 return bscal != 0; 273 } 274 /// 275 unittest 276 { 277 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 278 279 SmartInt!int n = -315; 280 assert( cast(bool)n); 281 282 n = 0; 283 assert(!cast(bool)n); 284 } 285 286 /** 287 Convert this value to type `M` using $(LINK2 ./package.html#to, `checkedint.to()`) for bounds checking. An 288 $(LINK2 ./flags.html, `IntFlag`) will be raised if `M` cannot represent the current value of this `SmartInt`. 289 **/ 290 M opCast(M)() const @safe 291 if (isCheckedInt!M || isIntegral!M || isSomeChar!M) 292 { 293 return to!(M, policy)(bscal); 294 } 295 /// 296 unittest 297 { 298 import checkedint.sticky : SmartInt; // use IntFlagPolicy.sticky 299 300 SmartInt!ulong n = 52; 301 auto a = cast(int)n; 302 static assert(is(typeof(a) == int)); 303 assert(!IntFlags.local); 304 assert(a == 52); 305 306 auto m = SmartInt!long(-1).mulPow2(n); 307 auto b = cast(wchar)m; 308 static assert(is(typeof(b) == wchar)); 309 assert(IntFlags.local.clear() == IntFlag.negOver); 310 } 311 312 /** 313 Convert this value to a type suitable for indexing an array: 314 $(UL 315 $(LI If `N` is signed, a `ptrdiff_t` is returned.) 316 $(LI If `N` is unsigned, a `size_t` is returned.) 317 ) 318 $(LINK2 ./package.html#to, `checkedint.to()`) is used for bounds checking. 319 **/ 320 @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const @safe 321 { 322 return to!(typeof(return), policy)(bscal); 323 } 324 /// 325 unittest 326 { 327 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 328 329 char[3] arr = ['a', 'b', 'c']; 330 SmartInt!long n = 1; 331 332 // On 32-bit, `long` cannot be used directly for array indexing, 333 static if (size_t.sizeof < long.sizeof) 334 static assert(!__traits(compiles, arr[n])); 335 // but idx can be used to concisely and safely cast to ptrdiff_t: 336 assert(arr[n.idx] == 'b'); 337 338 // The conversion is bounds checked: 339 static if (size_t.sizeof < long.sizeof) 340 { 341 n = long.min; 342 try 343 { 344 arr[n.idx] = '?'; 345 } 346 catch (CheckedIntException e) 347 { 348 assert(e.intFlags == IntFlag.negOver); 349 } 350 } 351 } 352 353 /// Get a simple hashcode for this value. 354 size_t toHash() const pure @safe nothrow @nogc 355 { 356 static if (N.sizeof > size_t.sizeof) 357 { 358 static assert(N.sizeof == (2 * size_t.sizeof)); 359 return cast(size_t)bscal ^ cast(size_t)(bscal >>> 32); 360 } 361 else 362 return cast(size_t)bscal; 363 } 364 365 /// Get a `string` representation of this value. 366 string toString() const @safe 367 { 368 return to!(string, IntFlagPolicy.sticky)(bscal); 369 } 370 /// 371 unittest 372 { 373 import checkedint.throws : smartInt; // use IntFlagPolicy.throws 374 assert(smartInt(-753).toString() == "-753"); 375 } 376 /** 377 Puts a `string` representation of this value into `w`. This overload will not allocate, unless 378 `std.range.primitives.put(w, ...)` allocates. 379 380 Params: 381 w = An output range that will receive the `string` 382 fmt = An optional format specifier 383 */ 384 void toString(Writer, Char = char)(Writer w, FormatSpec!Char fmt = (FormatSpec!Char).init) const 385 { 386 formatValue(w, bscal, fmt); 387 } 388 389 // Comparison ///////////////////////////////////////////////// 390 /// Returns `true` if this value is mathematically precisely equal to `right`. 391 bool opEquals(M)(const M right) const pure @safe nothrow @nogc 392 if (isCheckedInt!M || isScalarType!M) 393 { 394 return smartOp!(policy).cmp!"=="(this.bscal, right.bscal); 395 } 396 /** 397 Perform a mathematically correct comparison to `right`. 398 399 Returns: $(UL 400 $(LI `-1` if this value is less than `right`.) 401 $(LI ` 0` if this value is precisely equal to `right`.) 402 $(LI ` 1` if this value is greater than `right`.) 403 $(LI `float.nan` if `right` is a floating-point `nan` value.) 404 ) 405 **/ 406 auto opCmp(M)(const M right) const pure @safe nothrow @nogc 407 if (isFloatingPoint!M) 408 { 409 return 410 (bscal < right)? -1 : 411 (bscal > right)? 1 : 412 (bscal == right)? 0 : float.nan; 413 } 414 /// ditto 415 int opCmp(M)(const M right) const pure @safe nothrow @nogc 416 if (isCheckedInt!M || isScalarType!M) 417 { 418 return smartOp!(policy).cmp(this.bscal, right.bscal); 419 } 420 421 // Unary ///////////////////////////////////////////////// 422 /// See $(LINK2 ./package.html#smartOp, `smartOp`). 423 typeof(this) opUnary(string op)() const pure @safe nothrow @nogc 424 if (op == "~") 425 { 426 static assert(bitOps, 427 "Bitwise operations are disabled."); 428 429 return typeof(return)(smartOp!(policy).unary!op(bscal)); 430 } 431 /// ditto 432 SmartInt!(Signed!N, policy, bitOps) opUnary(string op)() const @safe 433 if (op == "+" || op == "-") 434 { 435 return typeof(return)(smartOp!(policy).unary!op(bscal)); 436 } 437 /// ditto 438 ref typeof(this) opUnary(string op)() return @safe 439 if (op.among!("++", "--")) 440 { 441 smartOp!(policy).unary!op(bscal); 442 return this; 443 } 444 445 /// ditto 446 SmartInt!(Unsigned!N, policy, bitOps) abs() const pure @safe nothrow @nogc 447 { 448 return typeof(return)(smartOp!(policy).abs(bscal)); 449 } 450 451 /// Count the number of set bits using `core.bitop.popcnt()`. 452 SmartInt!(int, policy, bitOps) popcnt()() const pure @safe nothrow @nogc 453 { 454 static assert(bitOps, "Bitwise operations are disabled."); 455 456 return typeof(return)(core.bitop.popcnt(bscal)); 457 } 458 459 /// See $(LINK2 ./package.html#smartOp, `smartOp`). 460 SmartInt!(ubyte, policy, bitOps) bsf()() const @safe 461 { 462 static assert(bitOps, "Bitwise operations are disabled."); 463 464 return typeof(return)(smartOp!(policy).bsf(bscal)); 465 } 466 /// ditto 467 SmartInt!(ubyte, policy, bitOps) bsr()() const @safe 468 { 469 static assert(bitOps, "Bitwise operations are disabled. Consider using ilogb() instead?"); 470 471 return typeof(return)(smartOp!(policy).bsr(bscal)); 472 } 473 474 /// ditto 475 SmartInt!(ubyte, policy, bitOps) ilogb() const @safe 476 { 477 return typeof(return)(smartOp!(policy).ilogb(bscal)); 478 } 479 480 // Binary ///////////////////////////////////////////////// 481 /// ditto 482 auto opBinaryRight(string op, M)(const M left) const pure @safe nothrow @nogc 483 if (isFloatingPoint!M) 484 { 485 return smartOp!(policy).binary!op(left, bscal); 486 } 487 /// ditto 488 auto opBinary(string op, M)(const M right) const pure @safe nothrow @nogc 489 if (isFloatingPoint!M) 490 { 491 return smartOp!(policy).binary!op(bscal, right); 492 } 493 /// ditto 494 auto opBinaryRight(string op, M)(const M left) const @safe 495 if (isSafeInt!M || isFixedPoint!M) 496 { 497 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 498 enum mixBitOps = bitOps && hasBitOps!M; 499 static assert(mixBitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 500 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 501 502 const wret = smartOp!(mixPolicy).binary!op(left.bscal, this.bscal); 503 return SmartInt!(typeof(wret), mixPolicy, mixBitOps)(wret); 504 } 505 /// ditto 506 auto opBinary(string op, M)(const M right) const @safe 507 if (isCheckedInt!M || isFixedPoint!M) 508 { 509 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 510 enum mixBitOps = bitOps && hasBitOps!M; 511 static assert(mixBitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 512 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 513 514 const wret = smartOp!(mixPolicy).binary!op(this.bscal, right.bscal); 515 return SmartInt!(typeof(wret), mixPolicy, mixBitOps)(wret); 516 } 517 /// ditto 518 ref typeof(this) opOpAssign(string op, M)(const M right) return @safe 519 if (isCheckedInt!M || isFixedPoint!M) 520 { 521 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 522 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 523 524 smartOp!(.max(policy, intFlagPolicyOf!M)).binary!(op ~ "=")(this.bscal, right.bscal); 525 return this; 526 } 527 528 /// ditto 529 auto mulPow2(M)(const M exp) const pure @safe nothrow @nogc 530 if (isFloatingPoint!M) 531 { 532 return smartOp!(policy).mulPow2(bscal, exp); 533 } 534 /// ditto 535 auto mulPow2(M)(const M exp) const @safe 536 if (isCheckedInt!M || isFixedPoint!M) 537 { 538 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 539 const wret = smartOp!(mixPolicy).mulPow2(this.bscal, exp.bscal); 540 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 541 } 542 /// ditto 543 auto divPow2(M)(const M exp) const pure @safe nothrow @nogc 544 if (isFloatingPoint!M) 545 { 546 return smartOp!(policy).divPow2(bscal, exp); 547 } 548 /// ditto 549 auto divPow2(M)(const M exp) const @safe 550 if (isCheckedInt!M || isFixedPoint!M) 551 { 552 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 553 const wret = smartOp!(mixPolicy).divPow2(this.bscal, exp.bscal); 554 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 555 } 556 /// ditto 557 auto modPow2(M)(const M exp) const pure @safe nothrow @nogc 558 if (isFloatingPoint!M) 559 { 560 return smartOp!(policy).modPow2(bscal, exp); 561 } 562 /// ditto 563 auto modPow2(M)(const M exp) const @safe 564 if (isCheckedInt!M || isFixedPoint!M) 565 { 566 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 567 const wret = smartOp!(mixPolicy).modPow2(this.bscal, exp.bscal); 568 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 569 } 570 571 /// Raise `this` to the `exp` power using `std.math.pow()`. 572 auto pow(M)(const M exp) const pure @safe nothrow @nogc 573 if (isFloatingPoint!M) 574 { 575 return std.math.pow(bscal, exp); 576 } 577 /// See $(LINK2 ./package.html#smartOp, `smartOp`). 578 auto pow(M)(const M exp) const @safe 579 if (isCheckedInt!M || isFixedPoint!M) 580 { 581 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 582 const wret = smartOp!(mixPolicy).pow(this.bscal, exp.bscal); 583 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 584 } 585 } 586 /// ditto 587 template SmartInt(N, IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps) 588 if ((isIntegral!N && !isUnqual!N) || isCheckedInt!N) 589 { 590 alias SmartInt = SmartInt!(BasicScalar!N, policy, bitOps); 591 } 592 /// 593 unittest 594 { 595 // Mixing standard signed and unsigned types is dangerous, but... 596 int ba = -1; 597 uint bb = 0; 598 assert(ba > bb); 599 600 auto bc = ba + bb; 601 assert(is(typeof(bc) == uint)); 602 assert(bc == 4294967295u); 603 604 // ...with SmartInt, mixed signed/unsigned operations "just work": 605 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 606 607 SmartInt!int ma = -1; 608 SmartInt!uint mb = 0; 609 assert(ma < mb); 610 611 auto mc = ma + mb; 612 assert(is(typeof(mc) == SmartInt!int)); 613 assert(mc != 4294967295u); 614 assert(mc == -1); 615 } 616 /// 617 unittest 618 { 619 // When IntFlagPolicy.throws is used, failed SmartInt operations will throw a CheckedIntException. 620 import checkedint.throws : SmartInt; 621 622 SmartInt!uint ma = 1; 623 SmartInt!uint mb = 0; 624 625 bool overflow = false; 626 try 627 { 628 SmartInt!uint mc = mb - ma; 629 assert(false); 630 } 631 catch (CheckedIntException e) 632 { 633 assert(e.intFlags == IntFlag.negOver); 634 overflow = true; 635 } 636 assert(overflow); 637 638 bool div0 = false; 639 try 640 { 641 // With standard integers, this would crash the program with an unrecoverable FPE... 642 SmartInt!uint mc = ma / mb; 643 assert(false); 644 } 645 catch (CheckedIntException e) 646 { 647 // ...but with SmartInt, it just throws a normal Exception. 648 assert(e.intFlags == IntFlag.div0); 649 div0 = true; 650 } 651 assert(div0); 652 } 653 /// 654 unittest 655 { 656 // When IntFlagPolicy.sticky is used, failed SmartInt operations set one or more bits in IntFlags.local. 657 import checkedint.sticky : SmartInt; 658 659 SmartInt!uint ma = 1; 660 SmartInt!uint mb = 0; 661 SmartInt!uint mc; 662 663 mc = mb - ma; 664 assert(IntFlags.local == IntFlag.over); 665 666 // With standard integers, this would crash the program with an unrecoverable FPE... 667 mc = ma / mb; 668 // ...but with SmartInt, it just sets a bit in IntFlags.local. 669 assert(IntFlags.local & IntFlag.div0); 670 671 // Each flag will remain set until cleared: 672 assert(IntFlags.local.clear() == (IntFlag.over | IntFlag.div0)); 673 assert(!IntFlags.local); 674 } 675 676 private template SmartInt(N, IntFlagPolicy policy, bool bitOps) 677 if (isIntegral!N) 678 { 679 alias SmartInt = SmartInt!( 680 Unqual!N, 681 policy, 682 cast(Flag!"bitOps")bitOps); 683 } 684 685 /// Get the value of `num` as a `SmartInt!N`. The integral type `N` can be infered from the argument. 686 SmartInt!(N, policy, bitOps) smartInt(IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps, N)(N num) @safe 687 if (isIntegral!N || isCheckedInt!N) 688 { 689 return typeof(return)(num.bscal); 690 } 691 /// 692 unittest 693 { 694 import checkedint.throws : smartInt, SmartInt; // use IntFlagPolicy.throws 695 696 auto a = smartInt(55uL); 697 static assert(is(typeof(a) == SmartInt!ulong)); 698 assert(a == 55); 699 } 700 701 /** 702 Implements various integer math operations with error checking. 703 704 `smartOp` strives to give the mathematically correct result, with integer-style rounding, for all inputs. Only 705 if the correct result is undefined or not representable by the return type is an error signalled, using 706 `checkedint.flags`. 707 708 The error-signalling policy may be selected using the `policy` template parameter. 709 **/ 710 template smartOp(IntFlagPolicy policy) 711 { 712 // NOTE: ddoc only scans the first branch of a static if 713 static if (policy == IntFlagPolicy.none) 714 { 715 // No need to redundantly instantiate members which don't depend on `policy`. 716 717 private void cmpTypeCheck(N, M)() pure nothrow @nogc 718 { 719 static assert(isBoolean!N == isBoolean!M, 720 "The intent of a direct comparison of " ~ 721 N.stringof ~ " with " ~ M.stringof ~ 722 " is unclear. Add an explicit cast." 723 ); 724 } 725 726 /** 727 Compare `left` and `right` using `op`. 728 $(UL 729 $(LI Unlike the standard integer comparison operator, this function correctly handles negative 730 values in signed/unsigned comparisons.) 731 $(LI Like the standard operator, comparisons involving any floating-point `nan` value always return 732 `false`.) 733 ) $(BR) 734 Direct comparisons between boolean values and numeric ones are forbidden. Make the intent explicit: 735 $(UL 736 $(LI `numeric == cast(N)boolean`) 737 $(LI `(numeric != 0) == boolean`) 738 ) 739 **/ 740 bool cmp(string op, N, M)(const N left, const M right) pure @safe nothrow @nogc 741 if (isScalarType!N && isScalarType!M) 742 { 743 cmpTypeCheck!(N, M)(); 744 745 static if (isSigned!N != isSigned!M) 746 { 747 static if (isSigned!N) 748 { 749 if (left < 0) 750 return mixin("-1 " ~ op ~ " 0"); 751 } 752 else 753 { 754 if (right < 0) 755 return mixin("0 " ~ op ~ " -1"); 756 } 757 } 758 759 return mixin("left " ~ op ~ " right"); 760 } 761 /// 762 unittest 763 { 764 import checkedint.sticky : smartOp; // smartOp.cmp() never throws 765 766 assert(uint.max == -1); 767 assert( smartOp.cmp!"!="(uint.max, -1)); 768 assert(-3156 > 300u); 769 assert( smartOp.cmp!"<"(-3156, 300u)); 770 771 assert(!smartOp.cmp!"<"(1, real.nan)); 772 assert(!smartOp.cmp!"<"(real.nan, 1)); 773 } 774 775 /** 776 Defines a total order on all basic scalar values, using the same rules as `std.math.cmp()`. 777 778 $(UL 779 $(LI Mixed signed/unsigned comparisons return the mathematically correct result.) 780 $(LI If neither `left` nor `right` is floating-point, this function is faster than 781 `std.math.cmp()`.) 782 $(LI If either `left` or `right` $(I is) floating-point, this function forwards to 783 `std.math.cmp()`.) 784 ) $(BR) 785 Direct comparisons between boolean values and numeric ones are forbidden. Make the intent explicit: 786 $(UL 787 $(LI `numeric == cast(N)boolean`) 788 $(LI `(numeric != 0) == boolean`) 789 ) 790 **/ 791 int cmp(N, M)(const N left, const M right) pure @safe nothrow @nogc 792 if (isScalarType!N && isScalarType!M) 793 { 794 cmpTypeCheck!(N, M)(); 795 796 static if (isFloatingPoint!N || isFloatingPoint!M) 797 { 798 return std.math.cmp(left, right); 799 } 800 else 801 { 802 static if (isSigned!N != isSigned!M) 803 { 804 static if (isSigned!N) 805 { 806 if (left < 0) 807 return -1; 808 } 809 else 810 { 811 if (right < 0) 812 return 1; 813 } 814 } 815 816 return (left < right)? -1 : (right < left); 817 } 818 } 819 /// 820 unittest 821 { 822 import checkedint.sticky : smartOp; // smartOp.cmp() never throws 823 824 assert(smartOp.cmp(325.0, 325u) == 0); 825 assert(smartOp.cmp(uint.max, -1) == 1); 826 assert(smartOp.cmp(-3156, 300u) == -1); 827 } 828 829 /// Get the absolute value of `num`. Because the return type is always unsigned, overflow is not possible. 830 Unsigned!N abs(N)(const N num) pure @safe nothrow @nogc 831 if (isIntegral!N) 832 { 833 static if (!isSigned!N) 834 return num; 835 else 836 return cast(typeof(return))(num < 0? 837 -cast(Promoted!N)num : // -num doesn't need to be checked for overflow 838 num); 839 } 840 /// ditto 841 IntFromChar!N abs(N)(const N num) pure @safe nothrow @nogc 842 if (isSomeChar!N) 843 { 844 return num; 845 } 846 /// 847 unittest 848 { 849 import checkedint.sticky : smartOp; // smartOp.abs() never throws 850 851 assert(smartOp.abs(int.min) == std.math.pow(2.0, 31)); 852 assert(smartOp.abs(-25) == 25u); 853 assert(smartOp.abs(745u) == 745u); 854 } 855 856 private template Result(N, string op, M) 857 if (isNumeric!N && isNumeric!M) 858 { 859 private: 860 enum reqFloat = isFloatingPoint!N || isFloatingPoint!M; 861 enum precN = precision!N, precM = precision!M; 862 enum precStd = reqFloat? precision!float : precision!uint; 863 enum smallSub = (op == "-") && precN < precision!int && precM < precision!int; 864 865 enum reqSign = reqFloat || 866 (op.among!("+", "-", "*" , "/") && (isSigned!N || isSigned!M || smallSub)) || 867 (op.among!("%", "^^", "<<", ">>", ">>>") && isSigned!N) || 868 (op.among!("&", "|", "^") && (isSigned!N && isSigned!M)); 869 870 enum reqPrec = reqFloat? max(precStd, precN, precM) : 871 op.among!("+", "-", "*")? max(precStd, precN, precM) - 1 : 872 op == "/"? (isSigned!M? max(precStd, precN) - 1 : precN) : 873 op == "%"? min(precision!N, precision!M) : 874 op == "^^"? max(precStd - 1, precN) : 875 op.among!("<<", ">>", ">>>")? precN : 876 /+op.among!("&", "|", "^")?+/ max(precN, precM); 877 878 public: 879 alias Result = Select!(reqFloat, 880 Select!(reqPrec <= double.mant_dig || double.mant_dig >= real.mant_dig, 881 Select!(reqPrec <= float.mant_dig, float, double), 882 real), 883 Select!(reqSign, 884 Select!(reqPrec <= 15, 885 Select!(reqPrec <= 7, byte, short), 886 Select!(reqPrec <= 31, int, long)), 887 Select!(reqPrec <= 16, 888 Select!(reqPrec <= 8, ubyte, ushort), 889 Select!(reqPrec <= 32, uint, ulong)))); 890 } 891 private template Result(N, string op, M) 892 if (isScalarType!N && isScalarType!M && 893 (!isNumeric!N || !isNumeric!M)) 894 { 895 alias Result = Result!(NumFromScal!N, op, NumFromScal!M); 896 } 897 } 898 else 899 { 900 alias cmp = smartOp!(IntFlagPolicy.none).cmp; 901 alias abs = smartOp!(IntFlagPolicy.none).abs; 902 private alias Result = smartOp!(IntFlagPolicy.none).Result; 903 } 904 905 /** 906 Perform the unary (single-argument) integer operation specified by `op`. 907 908 Key differences from the standard unary operators: 909 $(UL 910 $(LI `-` and `+` always return a signed value.) 911 $(LI `-` is checked for overflow, because `-int.min` is greater than `int.max`.) 912 $(LI `++` and `--` are checked for overflow.) 913 ) $(BR) 914 Note that like the standard operators, `++` and `--` take the operand by `ref` and overwrite its value with 915 the result. 916 **/ 917 N unary(string op, N)(const N num) pure @safe nothrow @nogc 918 if (isIntegral!N && op == "~") 919 { 920 return cast(N)~cast(Promoted!N)num; 921 } 922 /// ditto 923 IntFromChar!N unary(string op, N)(const N num) pure @safe nothrow @nogc 924 if (isSomeChar!N && op == "~") 925 { 926 return cast(N)~cast(Promoted!N)num; 927 } 928 /// ditto 929 Signed!(Promoted!N) unary(string op, N)(const N num) @safe 930 if (isFixedPoint!N && op.among!("-", "+")) 931 { 932 alias R = typeof(return); 933 alias UR = Unsigned!R; 934 935 static if (op == "-") 936 { 937 static if (isSigned!N) 938 { 939 if (num < -trueMax!R) 940 IntFlag.posOver.raise!policy(); 941 } 942 else 943 { 944 if (num > cast(UR)trueMin!R) 945 IntFlag.negOver.raise!policy(); 946 } 947 948 return -cast(R)num; 949 } 950 else 951 { 952 static if (!isSigned!N) 953 { 954 if (num > trueMax!R) 955 IntFlag.posOver.raise!policy(); 956 } 957 958 return num; 959 } 960 } 961 /// ditto 962 ref N unary(string op, N)(return ref N num) @safe 963 if (isIntegral!N && op.among!("++", "--")) 964 { 965 static if (op == "++") 966 { 967 if (num >= trueMax!N) 968 IntFlag.posOver.raise!policy(); 969 970 return ++num; 971 } 972 else static if (op == "--") 973 { 974 if (num <= trueMin!N) 975 IntFlag.negOver.raise!policy(); 976 977 return --num; 978 } 979 else 980 static assert(false); 981 } 982 /// 983 unittest 984 { 985 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 986 987 assert(smartOp.unary!"~"(0u) == uint.max); 988 989 auto a = smartOp.unary!"-"(20uL); 990 static assert(is(typeof(a) == long)); 991 assert(a == -20); 992 993 auto b = smartOp.unary!"+"(uint.max); 994 static assert(is(typeof(b) == int)); 995 assert(IntFlags.local.clear() == IntFlag.posOver); 996 997 uint c = 1u; 998 assert(smartOp.unary!"--"(c) == 0u); 999 assert(c == 0u); 1000 smartOp.unary!"--"(c); 1001 assert(IntFlags.local.clear() == IntFlag.negOver); 1002 1003 int d = 7; 1004 assert(smartOp.unary!"++"(d) == 8); 1005 assert(d == 8); 1006 } 1007 1008 /// `core.bitop.bsf` without the undefined behaviour. `smartOp.bsf(0)` will raise `IntFlag.undef`. 1009 ubyte bsf(N)(const N num) @safe 1010 if (isFixedPoint!N) 1011 { 1012 return cast(ubyte) bsfImpl!policy(num); 1013 } 1014 /// 1015 unittest 1016 { 1017 import checkedint.sticky : smartOp; 1018 1019 assert(smartOp.bsf(20) == 2); 1020 1021 smartOp.bsf(0); 1022 assert(IntFlags.local.clear() == IntFlag.undef); 1023 } 1024 1025 /// `core.bitop.bsr` without the undefined behaviour. `smartOp.bsr(0)` will raise `IntFlag.undef`. 1026 ubyte bsr(N)(const N num) @safe 1027 if (isFixedPoint!N) 1028 { 1029 return cast(ubyte) bsrImpl!policy(num); 1030 } 1031 /// 1032 unittest 1033 { 1034 import checkedint.sticky : smartOp; 1035 1036 assert(smartOp.bsr( 20) == 4); 1037 assert(smartOp.bsr(-20) == 31); 1038 1039 smartOp.bsr(0); 1040 assert(IntFlags.local.clear() == IntFlag.undef); 1041 } 1042 1043 /** 1044 Get the base 2 logarithm of `abs(num)`, rounded down to the nearest integer. 1045 1046 `smartOp.ilogb(0)` will raise `IntFlag.undef`. 1047 **/ 1048 ubyte ilogb(N)(const N num) @safe 1049 if (isFixedPoint!N) 1050 { 1051 return cast(ubyte) bsrImpl!policy(abs(num)); 1052 } 1053 /// 1054 unittest 1055 { 1056 import checkedint.sticky : smartOp; 1057 1058 assert(smartOp.ilogb(20) == 4); 1059 assert(smartOp.ilogb(-20) == 4); 1060 1061 smartOp.ilogb(0); 1062 assert(IntFlags.local.clear() == IntFlag.undef); 1063 } 1064 1065 private auto binaryImpl(string op, N, M)(const N left, const M right) @safe 1066 if (isIntegral!N && isIntegral!M) 1067 { 1068 enum wop = (op[$-1] == '=')? op[0 .. $-1] : op; 1069 alias 1070 UN = Unsigned!N, 1071 UM = Unsigned!M, 1072 W = Result!(N, wop, M), 1073 R = Select!(wop == op, W, N); 1074 1075 static if (wop.among!("+", "-", "*")) 1076 { 1077 enum safePrec = (wop == "*")? 1078 precision!N + precision!M : 1079 max(precision!N, precision!M) + 1; 1080 enum safeR = precision!R >= safePrec; 1081 1082 static if (safeR) 1083 return mixin("cast(R)left " ~ wop ~ " cast(R)right"); 1084 else 1085 { 1086 enum safeW = precision!W >= safePrec; 1087 enum matched = (isSigned!N == isSigned!M); 1088 enum oX = staticIndexOf!(wop, "+", "-", "*") << 1; 1089 alias cops = AliasSeq!(addu, adds, subu, subs, mulu, muls); 1090 1091 static if (safeW || matched || wop == "*") 1092 { 1093 bool over; 1094 static if (safeW) 1095 const wR = mixin("cast(W)left " ~ wop ~ " cast(W)right"); 1096 else 1097 { 1098 static if (matched) 1099 { 1100 alias cop = cops[oX + isSigned!W]; 1101 const wR = cop(left, right, over); 1102 } 1103 else 1104 { 1105 // integer multiplication is commutative 1106 static if (isSigned!N) 1107 W sa = left, ub = right; 1108 else 1109 W ub = left, sa = right; 1110 1111 static if (isSigned!R) 1112 { 1113 W wR = muls(sa, ub, over); 1114 if (ub < 0) 1115 over = (sa != 0) && (ub != trueMin!W || sa != -1); 1116 } 1117 else 1118 { 1119 over = (sa < 0) && (ub != 0); 1120 const wR = mulu(sa, ub, over); 1121 } 1122 } 1123 } 1124 1125 alias WR = typeof(wR); 1126 static if (isSigned!WR && trueMin!WR < trueMin!R) 1127 { 1128 if (wR < trueMin!R) 1129 over = true; 1130 } 1131 static if (trueMax!WR > trueMax!R) 1132 { 1133 if (wR > trueMax!R) 1134 over = true; 1135 } 1136 } 1137 else 1138 { 1139 alias UW = Unsigned!W; 1140 alias WR = Select!(isSigned!R, W, UW); 1141 alias cop = cops[oX]; 1142 1143 bool hiBit = false; 1144 const wR = cast(WR) cop(cast(UW)left, cast(UW)right, hiBit); 1145 const bool wSign = (Select!(isSigned!N, left, right) < 0) ^ hiBit; 1146 1147 static if (isSigned!WR) 1148 { 1149 static if (trueMax!WR > trueMax!R) 1150 { 1151 const over = (wR < 0)? 1152 !wSign || (wR < trueMin!R) : 1153 wSign || (wR > trueMax!R); 1154 } 1155 else 1156 const over = (wR < 0) != wSign; 1157 } 1158 else 1159 { 1160 static if (trueMax!WR > trueMax!R) 1161 const over = wSign || (wR > trueMax!R); 1162 else 1163 alias over = wSign; 1164 } 1165 } 1166 1167 if (over) 1168 IntFlag.over.raise!policy(); 1169 return cast(R) wR; 1170 } 1171 } 1172 else static if (wop == "/") 1173 { 1174 static if (!isSigned!N && !isSigned!M) 1175 { 1176 R ret = void; 1177 if (right == 0) 1178 { 1179 IntFlag.div0.raise!policy(); 1180 ret = 0; 1181 } 1182 else 1183 ret = cast(R)(left / right); 1184 1185 return ret; 1186 } 1187 else 1188 { 1189 alias P = Select!(precision!N <= 32 && precision!M <= 32, uint, ulong); 1190 1191 IntFlag flag; 1192 R ret = void; 1193 if (right == 0) 1194 { 1195 flag = IntFlag.div0; 1196 ret = 0; 1197 } 1198 else 1199 { 1200 static if (isSigned!N && isSigned!M) 1201 { 1202 if (left == trueMin!R && right == -1) 1203 { 1204 flag = IntFlag.posOver; 1205 ret = 0; 1206 } 1207 else 1208 ret = cast(R)(left / right); 1209 } 1210 else 1211 { 1212 alias UR = Unsigned!R; 1213 1214 P wL = void; 1215 P wG = void; 1216 static if (isSigned!N) 1217 { 1218 const negR = left < 0; 1219 alias side = left; 1220 alias wS = wL; 1221 wG = cast(P)right; 1222 } 1223 else 1224 { 1225 const negR = right < 0; 1226 wL = cast(P)left; 1227 alias side = right; 1228 alias wS = wG; 1229 } 1230 1231 if (negR) 1232 { 1233 wS = -cast(P)side; 1234 const P wR = wL / wG; 1235 1236 if (wR > cast(UR)trueMin!R) 1237 flag = IntFlag.negOver; 1238 ret = cast(R)-wR; 1239 } 1240 else 1241 { 1242 wS = cast(P)side; 1243 const P wR = wL / wG; 1244 1245 if (wR > cast(UR)trueMax!R) 1246 flag = IntFlag.posOver; 1247 ret = cast(R)wR; 1248 } 1249 } 1250 } 1251 1252 if (!flag.isNull) 1253 flag.raise!policy(); 1254 return ret; 1255 } 1256 } 1257 else static if (wop == "%") 1258 { 1259 R ret = void; 1260 static if (isSigned!M) 1261 const wG = cast(UM)((right < 0)? -cast(Promoted!M)right : right); 1262 else 1263 const wG = right; 1264 1265 if (wG <= trueMax!N) 1266 { 1267 if (wG) 1268 ret = cast(R)(left % cast(N)wG); 1269 else 1270 { 1271 IntFlag.div0.raise!policy(); 1272 ret = 0; 1273 } 1274 } 1275 else 1276 { 1277 static if (isSigned!N) 1278 { 1279 ret = (wG != cast(UN)trueMin!N || left != trueMin!N)? 1280 cast(R)left : 1281 cast(R)0; 1282 } 1283 else 1284 ret = cast(R)left; 1285 } 1286 1287 return ret; 1288 } 1289 else static if (wop.among!("<<", ">>", ">>>")) 1290 { 1291 const negG = right < 0; 1292 const shR = (wop == "<<")? 1293 negG : 1294 !negG; 1295 1296 R ret = void; 1297 static if (wop == ">>>") 1298 const wL = cast(UN)left; 1299 else 1300 alias wL = left; 1301 const absG = cast(UM) (negG? 1302 -cast(Promoted!M)right : 1303 right); 1304 1305 enum maxSh = precision!UN - 1; 1306 if (absG <= maxSh) 1307 { 1308 const wG = cast(int)absG; 1309 ret = cast(R)(shR? 1310 wL >> wG : 1311 wL << wG); 1312 } 1313 else 1314 { 1315 ret = cast(R)((isSigned!N && (wop != ">>>") && shR)? 1316 (wL >> maxSh) : 1317 0); 1318 } 1319 1320 return ret; 1321 } 1322 else static if (wop.among!("&", "|", "^")) 1323 return cast(R)mixin("left " ~ wop ~ " right"); 1324 else 1325 static assert(false); 1326 } 1327 1328 /** 1329 Perform the binary (two-argument) integer operation specified by `op`. 1330 1331 Key differences from the standard binary operators: 1332 $(UL 1333 $(LI `+`, `-`, `*`, `/`, and `%` return a signed type if the result could be negative, unless $(B both) 1334 inputs are unsigned.) 1335 $(LI `+`, `-`, `*`, and `/` are checked for overflow.) 1336 $(LI `/` and `%` are checked for divide-by-zero, and will never generate an FPE.) 1337 $(LI `<<`, `>>`, and `>>>` are well-defined for all possible values of `right`. Large shifts return the 1338 same result as shifting by `1` `right` times in a row. (But, much faster because no actual loop is 1339 used.)) 1340 ) $(BR) 1341 Note also: 1342 $(UL 1343 $(LI The shift operators are $(B not) checked for overflow and should not be used for 1344 multiplication, division, or exponentiation. Instead, use `mulPow2()` and `divPow2()`, which internally 1345 use the bitshifts for speed, but check for overflow and correctly handle negative values.) 1346 $(LI Likewise, `modPow2()` should be used for remainders instead of `&`.) 1347 $(LI `^^` and `^^=` will remain disabled in favour of `pow` until DMD issues 15288 and 15412 are fixed.) 1348 ) $(BR) 1349 Like the standard equivalents, the assignment operators (`+=`, `-=`, `*=`, etc.) take `left` by `ref` and will 1350 overwrite it with the result of the operation. 1351 **/ 1352 auto binary(string op, N, M)(const N left, const M right) @safe 1353 if (isFixedPoint!N && isFixedPoint!M && 1354 op.among!("+", "-", "*", "/", "%", "^^", "<<", ">>", ">>>", "&", "|", "^")) 1355 { 1356 static assert(op != "^^", 1357 "pow() should be used instead of operator ^^ because of issue 15288."); 1358 1359 return binaryImpl!(op, NumFromScal!N, NumFromScal!M)(left, right); 1360 } 1361 /// ditto 1362 ref N binary(string op, N, M)(return ref N left, const M right) @safe 1363 if (isIntegral!N && isFixedPoint!M && (op[$ - 1] == '=')) 1364 { 1365 static assert(op != "^^=", 1366 "pow() should be used instead of operator ^^= because of issue 15412."); 1367 1368 left = binaryImpl!(op, NumFromScal!N, NumFromScal!M)(left, right); 1369 return left; 1370 } 1371 /// 1372 unittest 1373 { 1374 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 1375 1376 ulong a = 18_446_744_073_709_551_615uL; 1377 long b = -6_744_073_709_551_615L; 1378 auto c = smartOp.binary!"+"(a, b); 1379 static assert(isSigned!(typeof(c))); 1380 assert(IntFlags.local.clear() == IntFlag.posOver); 1381 1382 assert(smartOp.binary!"+="(a, b) == 18_440_000_000_000_000_000uL); 1383 assert(a == 18_440_000_000_000_000_000uL); 1384 1385 uint d = 25u; 1386 int e = 32; 1387 auto f = smartOp.binary!"-"(d, e); 1388 static assert(isSigned!(typeof(f))); 1389 assert(f == -7); 1390 1391 smartOp.binary!"-="(d, e); 1392 assert(IntFlags.local.clear() == IntFlag.negOver); 1393 1394 uint g = 1u << 31; 1395 int h = -1; 1396 auto i = smartOp.binary!"*"(g, h); 1397 static assert(isSigned!(typeof(i))); 1398 assert(i == int.min); 1399 1400 smartOp.binary!"*="(g, h); 1401 assert(IntFlags.local.clear() == IntFlag.negOver); 1402 1403 long j = long.min; 1404 ulong k = 1uL << 63; 1405 auto m = smartOp.binary!"/"(j, k); 1406 static assert(isSigned!(typeof(m))); 1407 assert(m == -1); 1408 1409 smartOp.binary!"/="(j, -1); 1410 assert(IntFlags.local.clear() == IntFlag.posOver); 1411 1412 ushort n = 20u; 1413 ulong p = ulong.max; 1414 auto q = smartOp.binary!"%"(n, p); 1415 static assert(is(typeof(q) == ushort)); 1416 assert(q == 20u); 1417 1418 smartOp.binary!"%="(n, 0); 1419 assert(IntFlags.local.clear() == IntFlag.div0); 1420 } 1421 /// 1422 unittest 1423 { 1424 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 1425 1426 assert(smartOp.binary!"<<"(-0x80, -2) == -0x20); 1427 ubyte a = 0x3u; 1428 long b = long.max; 1429 auto c = smartOp.binary!"<<"(a, b); 1430 static assert(is(typeof(c) == ubyte)); 1431 assert(c == 0u); 1432 1433 assert(smartOp.binary!"<<="(a, 7) == 0x80u); 1434 assert(a == 0x80u); 1435 1436 short d = -0xC; 1437 ubyte e = 5u; 1438 auto f = smartOp.binary!">>"(d, e); 1439 static assert(is(typeof(f) == short)); 1440 assert(f == -0x1); 1441 1442 assert(smartOp.binary!">>="(d, -8) == -0xC00); 1443 assert(d == -0xC00); 1444 1445 int g = -0x80; 1446 ulong h = 2u; 1447 auto i = smartOp.binary!">>>"(g, h); 1448 static assert(is(typeof(i) == int)); 1449 assert(i == 0x3FFF_FFE0); 1450 1451 assert(smartOp.binary!">>>="(g, 32) == 0); 1452 assert(g == 0); 1453 1454 ubyte j = 0x6Fu; 1455 short k = 0x4076; 1456 auto m = smartOp.binary!"&"(j, k); 1457 static assert(is(typeof(m) == ushort)); 1458 assert(m == 0x66u); 1459 1460 assert(smartOp.binary!"&="(j, k) == 0x66u); 1461 assert(j == 0x66u); 1462 1463 byte n = 0x6F; 1464 ushort p = 0x4076u; 1465 auto q = smartOp.binary!"|"(n, p); 1466 static assert(is(typeof(q) == ushort)); 1467 assert(q == 0x407Fu); 1468 1469 assert(smartOp.binary!"|="(n, p) == 0x7F); 1470 assert(n == 0x7F); 1471 1472 int r = 0x6F; 1473 int s = 0x4076; 1474 auto t = smartOp.binary!"^"(r, s); 1475 static assert(is(typeof(t) == int)); 1476 assert(t == 0x4019); 1477 1478 assert(smartOp.binary!"^="(r, s) == 0x4019); 1479 assert(r == 0x4019); 1480 1481 assert(!IntFlags.local); 1482 } 1483 1484 /** 1485 Equivalent to `left * pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer 1486 alternative to `left << exp` that is still very fast. 1487 1488 Note that (conceptually) rounding occurs $(I after) the `*`, meaning that `mulPow2(left, -exp)` is 1489 equivalent to `divPow2(left, exp)`. 1490 **/ 1491 auto mulPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 1492 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 1493 { 1494 return byPow2Impl!("*", NumFromScal!N, NumFromScal!M)(left, exp); 1495 } 1496 /// ditto 1497 auto mulPow2(N, M)(const N left, const M exp) @safe 1498 if (isFixedPoint!N && isFixedPoint!M) 1499 { 1500 return byPow2Impl!("*", policy, NumFromScal!N, NumFromScal!M)(left, exp); 1501 } 1502 /// 1503 unittest 1504 { 1505 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 1506 1507 assert(smartOp.mulPow2(-23, 5) == -736); 1508 smartOp.mulPow2(10_000_000, 10); 1509 assert(IntFlags.local.clear() == IntFlag.posOver); 1510 1511 assert(smartOp.mulPow2(65536, -8) == 256); 1512 assert(smartOp.mulPow2(-100, -100) == 0); 1513 } 1514 1515 /** 1516 Equivalent to `left / pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer 1517 alternative to `left >> exp` that is still very fast. 1518 1519 Note that (conceptually) rounding occurs $(I after) the `/`, meaning that `divPow2(left, -exp)` is 1520 equivalent to `mulPow2(left, exp)`. 1521 **/ 1522 auto divPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 1523 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 1524 { 1525 return byPow2Impl!("/", NumFromScal!N, NumFromScal!M)(left, exp); 1526 } 1527 /// ditto 1528 auto divPow2(N, M)(const N left, const M exp) @safe 1529 if (isFixedPoint!N && isFixedPoint!M) 1530 { 1531 return byPow2Impl!("/", policy, NumFromScal!N, NumFromScal!M)(left, exp); 1532 } 1533 /// 1534 unittest 1535 { 1536 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 1537 1538 assert(smartOp.divPow2(65536, 8) == 256); 1539 assert(smartOp.divPow2(-100, 100) == 0); 1540 assert(smartOp.divPow2(-23, -5) == -736); 1541 1542 smartOp.divPow2(10_000_000, -10); 1543 assert(IntFlags.local.clear() == IntFlag.posOver); 1544 } 1545 1546 /** 1547 Equivalent to `left % pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer 1548 alternative to `left & ((1 << exp) - 1)` that is still very fast. 1549 **/ 1550 auto modPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 1551 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 1552 { 1553 return byPow2Impl!("%", NumFromScal!N, NumFromScal!M)(left, exp); 1554 } 1555 /// ditto 1556 auto modPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 1557 if (isFixedPoint!N && isFixedPoint!M) 1558 { 1559 return byPow2Impl!("%", IntFlagPolicy.sticky, NumFromScal!N, NumFromScal!M)(left, exp); 1560 } 1561 /// 1562 unittest 1563 { 1564 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 1565 1566 assert(smartOp.modPow2( 101, 1) == 1); 1567 assert(smartOp.modPow2( 101, 3) == 5); 1568 assert(smartOp.modPow2(-101, 3) == -5); 1569 1570 assert(smartOp.modPow2(101, -2) == 0); 1571 assert(smartOp.modPow2(101, 1_000) == 101); 1572 } 1573 1574 /** 1575 Raise `base` to the `exp` power. 1576 1577 Errors that may be signalled if neither input is floating-point: 1578 $(UL 1579 $(LI `IntFlag.posOver` or `IntFlag.negOver` if the absolute value of the result is too large to 1580 represent with the return type.) 1581 $(LI `IntFlag.div0` if `base == 0` and `exp < 0`.) 1582 ) 1583 **/ 1584 auto pow(N, M)(const N base, const M exp) pure @safe nothrow @nogc 1585 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 1586 { 1587 alias R = Result!(N, "^^", M); 1588 static assert(is(typeof(return) == R)); 1589 return std.math.pow(cast(R)base, exp); 1590 } 1591 /// ditto 1592 auto pow(N, M)(const N base, const M exp) @safe 1593 if (isFixedPoint!N && isFixedPoint!M) 1594 { 1595 alias R = Result!(N, "^^", M); 1596 1597 const po = powImpl!(R, Select!(isSigned!M, long, ulong))(base, exp); 1598 static assert(is(typeof(po.num) == const(R))); 1599 1600 if (!po.flag.isNull) 1601 po.flag.raise!policy(); 1602 return po.num; 1603 } 1604 /// 1605 unittest 1606 { 1607 import checkedint.sticky : smartOp; // use IntFlagPolicy.sticky 1608 1609 assert(smartOp.pow(-10, 3) == -1_000); 1610 assert(smartOp.pow(16, 4uL) == 65536); 1611 assert(smartOp.pow(2, -1) == 0); 1612 1613 smartOp.pow(-3, 27); 1614 assert(IntFlags.local.clear() == IntFlag.negOver); 1615 smartOp.pow(0, -5); 1616 assert(IntFlags.local.clear() == IntFlag.div0); 1617 } 1618 } 1619 private alias smartOp(bool throws) = smartOp!(cast(Flag!"throws")throws); 1620 1621 // debug ///////////////////////////////////////////////// 1622 /** 1623 `template` `alias` that evaluates to `SafeInt!(N, policy, bitOps)` in debug mode, and `N` in release mode. This 1624 way, `SafeInt!N` is used to debug integer logic while testing, but the basic `N` is used in release mode for 1625 maximum speed and the smallest binaries. 1626 1627 While this may be very helpful for debugging algorithms, note that `DebugInt` is $(B not) a substitute 1628 for input validation in release mode. Unrecoverable FPEs or silent data-corrupting overflow can still occur in 1629 release mode in algorithms that are faulty, or missing the appropriate manual bounds checks. 1630 1631 If performance is the only motivation for using `DebugInt` rather than 1632 $(LINK2 ./package.html#SmartInt, `SmartInt`), consider limiting `DebugInt` to only the hotest code paths - inner 1633 loops and the like. For most programs, this should provide nearly the same performance boost as using it 1634 everywhere, with far less loss of safety. 1635 **/ 1636 template DebugInt(N, IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps) 1637 if (isIntegral!N || isCheckedInt!N) 1638 { 1639 version (Debug) 1640 alias DebugInt = SafeInt!(N, policy, bitOps); 1641 else 1642 alias DebugInt = Unqual!(BasicScalar!N); 1643 } 1644 1645 // safe ///////////////////////////////////////////////// 1646 /** 1647 Wrapper for any basic integral type `N` that uses the checked operations from `safeOp` and rejects attempts to 1648 directly assign values that cannot be proven to be within the range representable by `N`. 1649 ($(LINK2 ./package.html#to, `checkedint.to()`) can be used to safely assign values of incompatible types, with 1650 runtime bounds checking.) 1651 1652 `SafeInt` is designed to be as interchangeable with `N` as possible, without compromising safety. The 1653 $(LINK2 ./package.html#DebugInt, `DebugInt`) `template` allows a variable to use `SafeInt` in debug mode to find 1654 bugs, and `N` directly in release mode for greater speed and a smaller binary. 1655 1656 Outside of generic code that needs to work with both `SafeInt!N` and `N`, it is generally preferable to use 1657 $(LINK2 ./package.html#SmartInt, `SmartInt`) instead. It generates far fewer error messages: mostly it 1658 "just works". 1659 1660 $(UL 1661 $(LI `policy` controls the error signalling policy (see `checkedint.flags`).) 1662 $(LI `bitOps` may be set to `No.bitOps` if desired, to turn bitwise operations on this type into a compile-time 1663 error.) 1664 ) 1665 **/ 1666 struct SafeInt(N, IntFlagPolicy _policy, Flag!"bitOps" bitOps = Yes.bitOps) 1667 if (isIntegral!N && isUnqual!N) 1668 { 1669 /// The error signalling policy used by this `SafeInt` type. 1670 enum IntFlagPolicy policy = _policy; 1671 1672 static if (bitOps) 1673 { 1674 /** 1675 The basic integral value of this `SafeInt`. Accessing this directly may be useful for: 1676 $(UL 1677 $(LI Intentionally doing modular (unchecked) arithmetic, or) 1678 $(LI Interacting with APIs that are not `checkedint` aware.) 1679 ) 1680 **/ 1681 N bscal; 1682 /// 1683 unittest 1684 { 1685 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws 1686 1687 SafeInt!uint n; 1688 static assert(is(typeof(n.bscal) == uint)); 1689 1690 n = 7u; 1691 assert(n.bscal == 7u); 1692 1693 n.bscal -= 8u; 1694 assert(n == uint.max); 1695 } 1696 1697 /// Get a view of this `SafeInt` that allows bitwise operations. 1698 @property ref inout(SafeInt!(N, policy, Yes.bitOps)) bits() return inout pure @safe nothrow @nogc 1699 { 1700 return this; 1701 } 1702 /// 1703 unittest 1704 { 1705 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws 1706 1707 SafeInt!(int, No.bitOps) n = 1; 1708 static assert(!__traits(compiles, n << 2)); 1709 assert(n.bits << 2 == 4); 1710 } 1711 } 1712 else 1713 { 1714 @property ref inout(N) bscal() return inout pure @safe nothrow @nogc 1715 { 1716 return bits.bscal; 1717 } 1718 SafeInt!(N, policy, Yes.bitOps) bits; 1719 } 1720 1721 /// The most negative possible value of this `SafeInt` type. 1722 enum SafeInt!(N, policy, bitOps) min = typeof(this)(trueMin!N); 1723 /// 1724 unittest 1725 { 1726 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws 1727 1728 assert(SafeInt!(int).min == int.min); 1729 assert(SafeInt!(uint).min == uint.min); 1730 } 1731 1732 /// The most positive possible value of this `SafeInt` type. 1733 enum SafeInt!(N, policy, bitOps) max = typeof(this)(trueMax!N); 1734 /// 1735 unittest 1736 { 1737 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws; 1738 1739 assert(SafeInt!(int).max == int.max); 1740 assert(SafeInt!(uint).max == uint.max); 1741 } 1742 1743 // Construction, assignment, and casting ///////////////////////////////////////////////// 1744 private void checkImplicit(M)() const @safe 1745 if (isCheckedInt!M || isScalarType!M) 1746 { 1747 alias MB = BasicScalar!M; 1748 static assert(trueMin!MB >= cast(real)N.min && MB.max <= cast(real)N.max, 1749 "SafeInt does not support implicit conversions from " ~ 1750 MB.stringof ~ " to " ~ N.stringof ~ 1751 ", because they are unsafe when unchecked. Use the explicit checkedint.to()." 1752 ); 1753 } 1754 1755 /** 1756 Assign the value of `that` to this `SafeInt` instance. 1757 1758 Trying to assign a value that cannot be proven at compile time to be representable by `N` is an error. Use 1759 $(LINK2 ./package.html#to, `checkedint.to()`) to safely convert `that` with runtime bounds checking, instead. 1760 **/ 1761 this(M)(const M that) pure @safe nothrow @nogc 1762 if (isCheckedInt!M || isScalarType!M) 1763 { 1764 checkImplicit!M(); 1765 this.bscal = that.bscal; 1766 } 1767 /// ditto 1768 ref typeof(this) opAssign(M)(const M that) return pure @safe nothrow @nogc 1769 if (isCheckedInt!M || isScalarType!M) 1770 { 1771 checkImplicit!M(); 1772 this.bscal = that.bscal; 1773 return this; 1774 } 1775 /// 1776 unittest 1777 { 1778 import checkedint.sticky : SafeInt, to; // use IntFlagPolicy.sticky 1779 1780 // Only types that for which N can represent all values are accepted directly: 1781 SafeInt!int n = int.max; 1782 n = byte.max; 1783 n = wchar.max; 1784 1785 // Values of a type that could be `< N.min` or `> N.max` are rejected at compile time: 1786 static assert(!__traits(compiles, n = long.max)); 1787 static assert(!__traits(compiles, n = uint.max)); 1788 static assert(!__traits(compiles, n = real.max)); 1789 1790 // Instead, use checkedint.to(), which does runtime bounds checking: 1791 n = to!int(315L); 1792 assert(n == 315); 1793 1794 n = to!int(long.max); 1795 assert(IntFlags.local.clear() == IntFlag.posOver); 1796 } 1797 1798 /** 1799 Convert this value to floating-point. This always succeeds, although some loss of precision may 1800 occur if M.sizeof <= N.sizeof. 1801 **/ 1802 M opCast(M)() const pure @safe nothrow @nogc 1803 if (isFloatingPoint!M) 1804 { 1805 return cast(M)bscal; 1806 } 1807 /// 1808 unittest 1809 { 1810 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws 1811 1812 SafeInt!int n = 92; 1813 auto f = cast(double)n; 1814 static assert(is(typeof(f) == double)); 1815 assert(f == 92.0); 1816 } 1817 1818 /// `this != 0` 1819 M opCast(M)() const pure @safe nothrow @nogc 1820 if (is(M == bool)) 1821 { 1822 return bscal != 0; 1823 } 1824 /// 1825 unittest 1826 { 1827 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws 1828 1829 SafeInt!int n = -315; 1830 assert( cast(bool)n); 1831 1832 n = 0; 1833 assert(!cast(bool)n); 1834 } 1835 1836 /** 1837 Convert this value to type `M` using $(LINK2 ./package.html#to, `checkedint.to()`) for bounds checking. An 1838 $(LINK2 ./flags.html, `IntFlag`) will be raised if `M` cannot represent the current value of this `SafeInt`. 1839 **/ 1840 M opCast(M)() const @safe 1841 if (isCheckedInt!M || isIntegral!M || isSomeChar!M) 1842 { 1843 return to!(M, policy)(bscal); 1844 } 1845 /// 1846 unittest 1847 { 1848 import checkedint.sticky : SafeInt; // use IntFlagPolicy.sticky 1849 1850 SafeInt!ulong n = 52uL; 1851 auto a = cast(int)n; 1852 static assert(is(typeof(a) == int)); 1853 assert(!IntFlags.local); 1854 assert(a == 52); 1855 1856 auto m = SafeInt!long(-1).mulPow2(n); 1857 auto b = cast(wchar)m; 1858 static assert(is(typeof(b) == wchar)); 1859 assert(IntFlags.local.clear() == IntFlag.negOver); 1860 } 1861 1862 /** 1863 Convert this value to a type suitable for indexing an array: 1864 $(UL 1865 $(LI If `N` is signed, a `ptrdiff_t` is returned.) 1866 $(LI If `N` is unsigned, a `size_t` is returned.) 1867 ) 1868 $(LINK2 ./package.html#to, `checkedint.to()`) is used for bounds checking. 1869 **/ 1870 @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const @safe 1871 { 1872 return to!(typeof(return), policy)(bscal); 1873 } 1874 /// 1875 unittest 1876 { 1877 import checkedint.throws : SafeInt; // use IntFlagPolicy.throws 1878 1879 char[3] arr = ['a', 'b', 'c']; 1880 SafeInt!long n = 1; 1881 1882 // On 32-bit, `long` cannot be used directly for array indexing, 1883 static if (size_t.sizeof < long.sizeof) 1884 static assert(!__traits(compiles, arr[n])); 1885 // but idx can be used to concisely and safely cast to ptrdiff_t: 1886 assert(arr[n.idx] == 'b'); 1887 1888 // The conversion is bounds checked: 1889 static if (size_t.sizeof < long.sizeof) 1890 { 1891 n = long.min; 1892 try 1893 { 1894 arr[n.idx] = '?'; 1895 } 1896 catch (CheckedIntException e) 1897 { 1898 assert(e.intFlags == IntFlag.negOver); 1899 } 1900 } 1901 } 1902 1903 /// Get a simple hashcode for this value. 1904 size_t toHash() const pure @safe nothrow @nogc 1905 { 1906 static if (N.sizeof > size_t.sizeof) 1907 { 1908 static assert(N.sizeof == (2 * size_t.sizeof)); 1909 return cast(size_t)bscal ^ cast(size_t)(bscal >>> 32); 1910 } 1911 else 1912 return cast(size_t)bscal; 1913 } 1914 1915 /// Get a `string` representation of this value. 1916 string toString() const @safe 1917 { 1918 return to!(string, IntFlagPolicy.sticky)(bscal); 1919 } 1920 /// 1921 unittest 1922 { 1923 import checkedint.throws : safeInt; // use IntFlagPolicy.throws 1924 assert(safeInt(-753).toString() == "-753"); 1925 } 1926 /** 1927 Puts a `string` representation of this value into `w`. This overload will not allocate, unless 1928 `std.range.primitives.put(w, ...)` allocates. 1929 1930 Params: 1931 w = An output range that will receive the `string` 1932 fmt = An optional format specifier 1933 */ 1934 void toString(Writer, Char = char)(Writer w, FormatSpec!Char fmt = (FormatSpec!Char).init) const 1935 { 1936 formatValue(w, bscal, fmt); 1937 } 1938 1939 // Comparison ///////////////////////////////////////////////// 1940 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 1941 bool opEquals(M)(const M right) const pure @safe nothrow @nogc 1942 if (isSafeInt!M || isScalarType!M) 1943 { 1944 return safeOp!(policy).cmp!"=="(this.bscal, right.bscal); 1945 } 1946 1947 /** 1948 Perform a floating-point comparison to `right`. 1949 1950 Returns: $(UL 1951 $(LI `-1` if this value is less than `right`.) 1952 $(LI ` 0` if this value is equal to `right`.) 1953 $(LI ` 1` if this value is greater than `right`.) 1954 $(LI `float.nan` if `right` is a floating-point `nan` value.)) 1955 **/ 1956 auto opCmp(M)(const M right) const pure @safe nothrow @nogc 1957 if (isFloatingPoint!M) 1958 { 1959 return 1960 (left < right)? -1 : 1961 (left > right)? 1 : 1962 (left == right)? 0 : float.nan; 1963 } 1964 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 1965 int opCmp(M)(const M right) const pure @safe nothrow @nogc 1966 if (isSafeInt!M || isFixedPoint!M) 1967 { 1968 return 1969 safeOp!(policy).cmp!"<"(this.bscal, right.bscal)? -1 : 1970 safeOp!(policy).cmp!">"(this.bscal, right.bscal); 1971 } 1972 1973 // Unary ///////////////////////////////////////////////// 1974 /// ditto 1975 SafeInt!(OpType!(op, N), policy, bitOps) opUnary(string op)() const @safe 1976 if (op.among!("-", "+", "~")) 1977 { 1978 static assert(bitOps || (op != "~"), 1979 "Bitwise operations are disabled."); 1980 1981 return typeof(return)(safeOp!(policy).unary!op(bscal)); 1982 } 1983 /// ditto 1984 ref typeof(this) opUnary(string op)() return @safe 1985 if (op.among!("++", "--")) 1986 { 1987 safeOp!(policy).unary!op(bscal); 1988 return this; 1989 } 1990 1991 /// ditto 1992 SafeInt!(CallType!(std.math.abs, N), policy, bitOps) abs() const @safe 1993 { 1994 return typeof(return)(safeOp!(policy).abs(bscal)); 1995 } 1996 1997 /// Count the number of set bits using `core.bitop.popcnt()`. 1998 SafeInt!(int, policy, bitOps) popcnt()() const pure @safe nothrow @nogc 1999 { 2000 static assert(bitOps, "Bitwise operations are disabled."); 2001 2002 return typeof(return)(core.bitop.popcnt(bscal)); 2003 } 2004 2005 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 2006 SafeInt!(int, policy, bitOps) bsf()() const @safe 2007 { 2008 static assert(bitOps, "Bitwise operations are disabled."); 2009 2010 return typeof(return)(safeOp!(policy).bsf(bscal)); 2011 } 2012 /// ditto 2013 SafeInt!(int, policy, bitOps) bsr()() const @safe 2014 { 2015 static assert(bitOps, "Bitwise operations are disabled. Consider using ilogb() instead?"); 2016 2017 return typeof(return)(safeOp!(policy).bsr(bscal)); 2018 } 2019 2020 /// ditto 2021 SafeInt!(int, policy, bitOps) ilogb() const @safe 2022 { 2023 return typeof(return)(safeOp!(policy).ilogb(bscal)); 2024 } 2025 2026 // Binary ///////////////////////////////////////////////// 2027 /// Perform a floating-point math operation. 2028 M opBinaryRight(string op, M)(const M left) const pure @safe nothrow @nogc 2029 if (isFloatingPoint!M) 2030 { 2031 return mixin("left " ~ op ~ " bscal"); 2032 } 2033 /// ditto 2034 M opBinary(string op, M)(const M right) const pure @safe nothrow @nogc 2035 if (isFloatingPoint!M) 2036 { 2037 return mixin("bscal " ~ op ~ " right"); 2038 } 2039 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 2040 SafeInt!(OpType!(M, op, N), policy, bitOps) opBinaryRight(string op, M)(const M left) const @safe 2041 if (isFixedPoint!M) 2042 { 2043 static assert(bitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 2044 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 2045 2046 return typeof(return)(safeOp!(policy).binary!op(left, bscal)); 2047 } 2048 /// ditto 2049 SafeInt!(OpType!(N, op, BasicScalar!M), .max(policy, intFlagPolicyOf!M), bitOps && hasBitOps!M) opBinary(string op, M)(const M right) const @safe 2050 if (isSafeInt!M || isFixedPoint!M) 2051 { 2052 static assert(bitOps && hasBitOps!M || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 2053 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 2054 2055 return typeof(return)(safeOp!(.max(policy, intFlagPolicyOf!M)).binary!op(this.bscal, right.bscal)); 2056 } 2057 /// ditto 2058 ref typeof(this) opOpAssign(string op, M)(const M right) return @safe 2059 if (isCheckedInt!M || isFixedPoint!M) 2060 { 2061 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 2062 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 2063 checkImplicit!(OpType!(N, op, BasicScalar!M))(); 2064 2065 safeOp!(.max(policy, intFlagPolicyOf!M)).binary!(op ~ "=")(this.bscal, right.bscal); 2066 return this; 2067 } 2068 2069 /// ditto 2070 auto mulPow2(M)(const M exp) const pure @safe nothrow @nogc 2071 if (isFloatingPoint!M) 2072 { 2073 return safeOp!(policy).mulPow2(bscal, exp); 2074 } 2075 /// ditto 2076 auto mulPow2(M)(const M exp) const @safe 2077 if (isCheckedInt!M || isFixedPoint!M) 2078 { 2079 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 2080 const wret = safeOp!(mixPolicy).mulPow2(this.bscal, exp.bscal); 2081 return SafeInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 2082 } 2083 /// ditto 2084 auto divPow2(M)(const M exp) const pure @safe nothrow @nogc 2085 if (isFloatingPoint!M) 2086 { 2087 return safeOp!(policy).divPow2(bscal, exp); 2088 } 2089 /// ditto 2090 auto divPow2(M)(const M exp) const @safe 2091 if (isCheckedInt!M || isFixedPoint!M) 2092 { 2093 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 2094 const wret = safeOp!(mixPolicy).divPow2(this.bscal, exp.bscal); 2095 return SafeInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 2096 } 2097 /// ditto 2098 auto modPow2(M)(const M exp) const pure @safe nothrow @nogc 2099 if (isFloatingPoint!M) 2100 { 2101 return safeOp!(policy).modPow2(bscal, exp); 2102 } 2103 /// ditto 2104 auto modPow2(M)(const M exp) const @safe 2105 if (isCheckedInt!M || isFixedPoint!M) 2106 { 2107 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 2108 const wret = safeOp!(mixPolicy).modPow2(this.bscal, exp.bscal); 2109 return SafeInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 2110 } 2111 2112 /// Raise `this` to the `exp` power using `std.math.pow()`. 2113 M pow(M)(const M exp) const pure @safe nothrow @nogc 2114 if (isFloatingPoint!M) 2115 { 2116 return std.math.pow(bscal, exp); 2117 } 2118 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 2119 SafeInt!(CallType!(std.math.pow, N, BasicScalar!M), .max(policy, intFlagPolicyOf!M), bitOps && hasBitOps!M) pow(M)(const M exp) const @safe 2120 if (isCheckedInt!M || isFixedPoint!M) 2121 { 2122 return typeof(return)(safeOp!(.max(policy, intFlagPolicyOf!M)).pow(this.bscal, exp.bscal)); 2123 } 2124 } 2125 /// ditto 2126 template SafeInt(N, IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps) 2127 if ((isIntegral!N && !isUnqual!N) || isCheckedInt!N) 2128 { 2129 alias SafeInt = SafeInt!(BasicScalar!N, policy, bitOps); 2130 } 2131 /// 2132 unittest 2133 { 2134 // Mixing standard signed and unsigned types is dangerous... 2135 int ba = -1; 2136 uint bb = 0; 2137 assert(ba > bb); 2138 2139 auto bc = ba + bb; 2140 assert(is(typeof(bc) == uint)); 2141 assert(bc == 4294967295u); 2142 2143 // ...that's why SafeInt doesn't allow it. 2144 import checkedint.throws : SafeInt, to; // use IntFlagPolicy.throws 2145 2146 SafeInt!int sa = -1; 2147 SafeInt!uint sb = 0u; 2148 static assert(!__traits(compiles, sa < sb)); 2149 static assert(!__traits(compiles, sa + sb)); 2150 2151 // Instead, use checkedint.to() to safely convert to a common type... 2152 auto sbi = to!(SafeInt!int)(sb); 2153 assert(sa < sbi); 2154 auto sc = sa + sbi; 2155 assert(sc == -1); 2156 // (...or just switch to SmartInt.) 2157 } 2158 /// 2159 unittest 2160 { 2161 // When IntFlagPolicy.throws is set, SafeInt operations that fail at runtime will throw a CheckedIntException. 2162 import checkedint.throws : SafeInt; 2163 2164 SafeInt!uint sa = 1u; 2165 SafeInt!uint sb = 0u; 2166 2167 bool overflow = false; 2168 try 2169 { 2170 SafeInt!uint sc = sb - sa; 2171 assert(false); 2172 } 2173 catch (CheckedIntException e) 2174 { 2175 assert(e.intFlags == IntFlag.negOver); 2176 overflow = true; 2177 } 2178 assert(overflow); 2179 2180 bool div0 = false; 2181 try 2182 { 2183 // With standard integers, this would crash the program with an unrecoverable FPE... 2184 SafeInt!uint sc = sa / sb; 2185 assert(false); 2186 } 2187 catch (CheckedIntException e) 2188 { 2189 // ...but with SafeInt, it just throws a normal Exception. 2190 assert(e.intFlags == IntFlag.div0); 2191 div0 = true; 2192 } 2193 assert(div0); 2194 } 2195 /// 2196 unittest 2197 { 2198 // When IntFlagPolicy.sticky is set, SafeInt operations that fail at runtime set one or more bits in IntFlags.local. 2199 import checkedint.sticky : SafeInt; 2200 2201 SafeInt!uint sa = 1u; 2202 SafeInt!uint sb = 0u; 2203 SafeInt!uint sc; 2204 2205 sc = sb - sa; 2206 assert(IntFlags.local == IntFlag.over); 2207 2208 // With standard integers, this would crash the program with an unrecoverable FPE... 2209 sc = sa / sb; 2210 // ...but with SmartInt, it just sets a bit in IntFlags.local. 2211 assert(IntFlags.local & IntFlag.div0); 2212 2213 // Each flag will remain set until cleared: 2214 assert(IntFlags.local.clear() == (IntFlag.over | IntFlag.div0)); 2215 assert(!IntFlags.local); 2216 } 2217 2218 private template SafeInt(N, IntFlagPolicy policy, bool bitOps) 2219 if (isIntegral!N) 2220 { 2221 alias SafeInt = SafeInt!( 2222 Unqual!N, 2223 policy, 2224 cast(Flag!"bitOps")bitOps); 2225 } 2226 2227 /// Get the value of `num` as a `SafeInt!N`. The integral type `N` can be infered from the argument. 2228 SafeInt!(N, policy, bitOps) safeInt(IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps, N)(N num) @safe 2229 if (isIntegral!N || isCheckedInt!N) 2230 { 2231 return typeof(return)(num.bscal); 2232 } 2233 /// 2234 unittest 2235 { 2236 import checkedint.throws : safeInt, SafeInt; // use IntFlagPolicy.throws 2237 2238 auto a = safeInt(55uL); 2239 static assert(is(typeof(a) == SafeInt!ulong)); 2240 assert(a == 55u); 2241 } 2242 2243 /** 2244 Implements various integer math operations with error checking. 2245 2246 `safeOp` strives to mimic the standard integer math operations in every way, except: 2247 $(UL 2248 $(LI If the operation is generally untrustworthy - for example, signed/unsigned comparisons - a compile-time error 2249 is generated. The message will usually suggest a workaround.) 2250 $(LI At runtime, if the result is mathematically incorrect an appropriate $(LINK2 ./flags.html, `IntFlag`) 2251 will be raised.) 2252 ) 2253 The runtime error-signalling policy may be selected using the `policy` template parameter. 2254 **/ 2255 template safeOp(IntFlagPolicy policy) 2256 { 2257 // NOTE: ddoc only scans the first branch of a static if 2258 static if (policy == IntFlagPolicy.none) 2259 { 2260 // No need to redundantly instantiate members which don't depend on `policy`. 2261 2262 private void cmpTypeCheck(N, M)() pure @safe nothrow @nogc 2263 { 2264 static assert(isBoolean!N == isBoolean!M, 2265 "The intent of a direct comparison of " ~ 2266 N.stringof ~ " with " ~ M.stringof ~ 2267 " is unclear. Add an explicit cast." 2268 ); 2269 2270 alias OT = OpType!(N, "+", M); 2271 static assert(isFloatingPoint!OT || isSigned!OT || !(isSigned!N || isSigned!M), 2272 "The standard signed/unsigned comparisons of " ~ N.stringof ~ " to " ~ M.stringof ~ 2273 " are unsafe. Use an explicit cast, or switch to smartOp/SmartInt." 2274 ); 2275 } 2276 2277 /** 2278 Compare `left` and `right` using `op`. 2279 2280 Unsafe signed/unsigned comparisons will trigger a compile-time error. Possible solutions include: 2281 $(UL 2282 $(LI Should the inputs really have different signedness? Changing the type of one to match the other 2283 is the simplest solution.) 2284 $(LI Consider using `smartOp.cmp()`, instead, as it can safely do signed/unsigned comparisons.) 2285 $(LI Alternately, $(LINK2 ./package.html#to, `checkedint.to()`) can be used to safely convert the 2286 type of one input, with runtime bounds checking.) 2287 ) $(BR) 2288 Direct comparisons between boolean values and numeric ones are also forbidden. Make the intent explicit: 2289 $(UL 2290 $(LI `numeric == cast(N)boolean`) 2291 $(LI `(numeric != 0) == boolean`) 2292 ) 2293 **/ 2294 bool cmp(string op, N, M)(const N left, const M right) pure @safe nothrow @nogc 2295 if (isScalarType!N && isScalarType!M) 2296 { 2297 cmpTypeCheck!(N, M)(); 2298 return mixin("left " ~ op ~ " right"); 2299 } 2300 /// 2301 unittest 2302 { 2303 import checkedint.sticky : safeOp; // safeOp.cmp() never throws 2304 2305 assert(safeOp.cmp!"=="(int.max, 0x7FFF_FFFF)); 2306 assert(safeOp.cmp!"!="(uint.min, 5u)); 2307 assert(safeOp.cmp!"<="(int.min, 0)); 2308 2309 static assert(!__traits(compiles, safeOp.cmp!"=="(uint.max, -1))); 2310 static assert(!__traits(compiles, safeOp.cmp!">"(-1, 1u))); 2311 } 2312 } 2313 else 2314 alias cmp = safeOp!(IntFlagPolicy.none).cmp; 2315 2316 /** 2317 Perform the unary (single-argument) integer operation specified by `op`. 2318 2319 Trying to negate `-` an unsigned value will generate a compile-time error, because mathematically, the result should 2320 always be negative (except for -0), but the unsigned return type cannot represent this. 2321 2322 `++` and `--` are checked for overflow at runtime, and will raise `IntFlag.posOver` or `IntFlag.negOver` if needed. 2323 **/ 2324 OpType!(op, N) unary(string op, N)(const N num) @safe 2325 if ((isFixedPoint!N && isIntegral!(OpType!(op, N))) && op.among!("-", "+", "~")) 2326 { 2327 static assert(isSigned!(typeof(return)) || op != "-", 2328 "The standard unary - operation for " ~ N.stringof ~ 2329 " is unsafe. Use an explicit cast to a signed type, or switch to smartOp/SmartInt." 2330 ); 2331 2332 static if (op == "-" && typeof(return).max <= N.max) 2333 { 2334 static assert(is(typeof(return) == N)); 2335 static if (is(N == int) || is(N == long)) 2336 { 2337 bool over = false; 2338 const N ret = negs(num, over); 2339 } 2340 else 2341 { 2342 const over = (num <= trueMin!N); 2343 const ret = -cast(Promoted!N)num; 2344 } 2345 2346 if (over) 2347 IntFlag.posOver.raise!policy(); 2348 } 2349 else 2350 const ret = mixin(op ~ "cast(Promoted!N)num"); 2351 2352 return cast(typeof(return))ret; 2353 } 2354 /// ditto 2355 ref N unary(string op, N)(return ref N num) @safe 2356 if (isIntegral!N && op.among!("++", "--")) 2357 { 2358 static if (op == "++") 2359 { 2360 if (num >= trueMax!N) 2361 IntFlag.posOver.raise!policy(); 2362 } 2363 else static if (op == "--") 2364 { 2365 if (num <= trueMin!N) 2366 IntFlag.negOver.raise!policy(); 2367 } 2368 2369 return mixin(op ~ "num"); 2370 } 2371 /// 2372 unittest 2373 { 2374 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2375 2376 assert(safeOp.unary!"~"(0u) == uint.max); 2377 2378 assert(safeOp.unary!"-"(20L) == -20L); 2379 static assert(!__traits(compiles, safeOp.unary!"-"(20uL))); 2380 safeOp.unary!"-"(long.min); 2381 assert(IntFlags.local.clear() == IntFlag.posOver); 2382 2383 auto a = safeOp.unary!"+"(uint.max); 2384 static assert(is(typeof(a) == uint)); 2385 assert(a == uint.max); 2386 2387 uint b = 1u; 2388 assert(safeOp.unary!"--"(b) == 0u); 2389 assert(b == 0u); 2390 safeOp.unary!"--"(b); 2391 assert(IntFlags.local.clear() == IntFlag.negOver); 2392 2393 int c = 7; 2394 assert(safeOp.unary!"++"(c) == 8); 2395 assert(c == 8); 2396 } 2397 2398 /** 2399 Get the absolute value of `num`. 2400 2401 `IntFlag.posOver` is raised if `N` is signed and `num == N.min`. 2402 **/ 2403 CallType!(std.math.abs, N) abs(N)(const N num) @safe 2404 if (isFixedPoint!N) 2405 { 2406 enum bool canOverflow = (typeof(return).max < cast(Unsigned!(Promoted!N))N.min); 2407 static assert(!canOverflow || is(typeof(return) == N)); 2408 2409 auto ret = cast(Promoted!N)num; 2410 static if (canOverflow && (is(N == int) || is(N == long))) 2411 { 2412 bool over = false; 2413 if (ret < 0) 2414 ret = negs(ret, over); 2415 } 2416 else 2417 { 2418 static if (canOverflow) 2419 const over = (ret <= trueMin!N); 2420 2421 static if (isSigned!N) 2422 { 2423 if (ret < 0) 2424 ret = -ret; 2425 } 2426 } 2427 2428 static if (canOverflow) 2429 { 2430 if (over) 2431 IntFlag.posOver.raise!policy(); 2432 } 2433 return cast(typeof(return))ret; 2434 } 2435 /// 2436 unittest 2437 { 2438 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2439 2440 assert(safeOp.abs(-25) == 25); 2441 assert(safeOp.abs(745u) == 745u); 2442 2443 safeOp.abs(int.min); 2444 assert(IntFlags.local.clear() == IntFlag.posOver); 2445 } 2446 2447 /// `core.bitop.bsf` without the undefined behaviour. `safeOp.bsf(0)` will raise `IntFlag.undef`. 2448 int bsf(N)(const N num) @safe 2449 if (isFixedPoint!N) 2450 { 2451 return bsfImpl!policy(num); 2452 } 2453 /// 2454 unittest 2455 { 2456 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2457 2458 assert(safeOp.bsf(20) == 2); 2459 2460 safeOp.bsf(0); 2461 assert(IntFlags.local.clear() == IntFlag.undef); 2462 } 2463 2464 /// `core.bitop.bsr` without the undefined behaviour. `safeOp.bsr(0)` will raise `IntFlag.undef`. 2465 int bsr(N)(const N num) @safe 2466 if (isFixedPoint!N) 2467 { 2468 return bsrImpl!policy(num); 2469 } 2470 /// 2471 unittest 2472 { 2473 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2474 2475 assert(safeOp.bsr( 20) == 4); 2476 assert(safeOp.bsr(-20) == 31); 2477 2478 safeOp.bsr(0); 2479 assert(IntFlags.local.clear() == IntFlag.undef); 2480 } 2481 2482 /** 2483 Get the base 2 logarithm of `abs(num)`, rounded down to the nearest integer. 2484 2485 `safeOp.ilogb(0)` will raise `IntFlag.undef`. 2486 **/ 2487 int ilogb(N)(const N num) @safe 2488 if (isFixedPoint!N) 2489 { 2490 static if (isSigned!N) 2491 const absN = cast(Unsigned!N) (num < 0? -cast(Promoted!N)num : num); 2492 else 2493 alias absN = num; 2494 2495 return bsrImpl!policy(absN); 2496 } 2497 /// 2498 unittest 2499 { 2500 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2501 2502 assert(safeOp.ilogb( 20) == 4); 2503 assert(safeOp.ilogb(-20) == 4); 2504 2505 safeOp.ilogb(0); 2506 assert(IntFlags.local.clear() == IntFlag.undef); 2507 } 2508 2509 private auto binaryImpl(string op, N, M)(const N left, const M right) @safe 2510 if (isFixedPoint!N && isFixedPoint!M) 2511 { 2512 enum wop = (op[$-1] == '=')? op[0 .. $-1] : op; 2513 alias P = OpType!(N, wop, M); 2514 alias R = Select!(wop == op, P, N); 2515 2516 static if (wop.among!("+", "-", "*")) 2517 { 2518 enum isPromSafe = !(isSigned!N || isSigned!M) || (isSigned!P && isSigned!R); 2519 enum needCOp = (wop == "*")? 2520 (precision!N + precision!M) > precision!P : 2521 (max(precision!N, precision!M) + 1) > precision!P; 2522 2523 bool over = false; 2524 static if (needCOp) 2525 { 2526 enum cx = (staticIndexOf!(wop, "+", "-", "*") << 1) + isSigned!P; 2527 alias cop = AliasSeq!(addu, adds, subu, subs, mulu, muls)[cx]; 2528 2529 const pR = cop(cast(P)left, cast(P)right, over); 2530 } 2531 else 2532 const pR = mixin("left " ~ wop ~ " right"); 2533 2534 static if (isSigned!P && trueMin!P < trueMin!R) 2535 { 2536 if (pR < trueMin!R) 2537 over = true; 2538 } 2539 static if (trueMax!P > trueMax!R) 2540 { 2541 if (pR > trueMax!R) 2542 over = true; 2543 } 2544 2545 if (over) 2546 IntFlag.over.raise!policy(); 2547 return cast(R)pR; 2548 } 2549 else static if (wop.among!("/", "%")) 2550 { 2551 enum isPromSafe = !(isSigned!N || isSigned!M) || 2552 (isSigned!P && (wop == "%"? (isSigned!R || !isSigned!N) : isSigned!R)); 2553 2554 const div0 = (right == 0); 2555 static if (isSigned!N && isSigned!M) 2556 const posOver = (left == trueMin!R) && (right == -1); 2557 else 2558 enum posOver = false; 2559 2560 R ret = void; 2561 if (div0 || posOver) 2562 { 2563 (posOver? IntFlag.posOver : IntFlag.div0).raise!policy(); 2564 ret = 0; // Prevent unrecoverable FPE 2565 } 2566 else 2567 ret = cast(R)mixin("left " ~ wop ~ " right"); 2568 2569 return ret; 2570 } 2571 else static if (wop.among!("<<", ">>", ">>>")) 2572 { 2573 enum isPromSafe = !isSigned!N || isSigned!R || (op == ">>>"); 2574 2575 enum invalidSh = ~cast(Promoted!M)(8 * P.sizeof - 1); 2576 if (right & invalidSh) 2577 IntFlag.undef.raise!policy(); 2578 2579 return cast(R) mixin("cast(P)left " ~ wop ~ " right"); 2580 } 2581 else static if (wop.among!("&", "|", "^")) 2582 { 2583 enum isPromSafe = true; 2584 2585 return cast(R)mixin("left " ~ wop ~ " right"); 2586 } 2587 else 2588 static assert(false); 2589 2590 static assert(isPromSafe, 2591 "The standard " ~ N.stringof ~ " " ~ op ~ " " ~ M.stringof ~ 2592 " operation is unsafe, due to a signed/unsigned mismatch. " ~ 2593 "Use an explicit cast, or switch to smartOp/SmartInt." 2594 ); 2595 } 2596 2597 /** 2598 Perform the binary (two-argument) integer operation specified by `op`. 2599 $(UL 2600 $(LI Unsafe signed/unsigned operations will generate a compile-time error.) 2601 $(LI `+`, `-`, `*`, `/`, and `%` are checked for overflow at runtime.) 2602 $(LI `/` and `%` are also checked for divide-by-zero.) 2603 $(LI `<<`, `>>`, and `>>>` are checked to verify that `right >= 0` and `right < (8 * typeof(left).sizeof)`. 2604 Otherwise, `IntFlag.undef` is raised.) 2605 ) $(BR) 2606 Note also: 2607 $(UL 2608 $(LI The shift operators are $(B not) checked for overflow and should not be used for multiplication, 2609 division, or exponentiation. Instead, use `mulPow2()` and `divPow2()`, which internally use the bitshifts for speed, 2610 but check for overflow and correctly handle negative values.) 2611 $(LI Likewise, `modPow2()` should be used for remainders instead of `&`.) 2612 $(LI `^^` and `^^=` will remain disabled in favour of `pow` until DMD issues 15288 and 15412 are fixed.) 2613 ) $(BR) 2614 Like the standard equivalents, the assignment operators (`+=`, `-=`, `*=`, etc.) take `left` by `ref` and will overwrite 2615 it with the result of the operation. 2616 **/ 2617 OpType!(N, op, M) binary(string op, N, M)(const N left, const M right) @safe 2618 if (isFixedPoint!N && isFixedPoint!M && 2619 op.among!("+", "-", "*", "/", "%", "^^", "<<", ">>", ">>>", "&", "|", "^")) 2620 { 2621 static assert(op != "^^", 2622 "pow() should be used instead of operator ^^ because of issue 15288."); 2623 2624 return binaryImpl!op(left, right); 2625 } 2626 /// ditto 2627 ref N binary(string op, N, M)(return ref N left, const M right) @safe 2628 if (isIntegral!N && isFixedPoint!M && (op[$ - 1] == '=')) 2629 { 2630 static assert(op != "^^=", 2631 "pow() should be used instead of operator ^^= because of issue 15412."); 2632 2633 left = binaryImpl!op(left, right); 2634 return left; 2635 } 2636 /// 2637 unittest 2638 { 2639 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2640 2641 assert(safeOp.binary!"+"(17, -5) == 12); 2642 static assert(!__traits(compiles, safeOp.binary!"+"(-1, 1u))); 2643 2644 ulong a = 18_446_744_073_709_551_615uL; 2645 safeOp.binary!"+="(a, 1u); 2646 assert(IntFlags.local.clear() == IntFlag.posOver); 2647 2648 assert(safeOp.binary!"-"(17u, 5u) == 12u); 2649 safeOp.binary!"-"(5u, 17u); 2650 assert(IntFlags.local.clear() == IntFlag.negOver); 2651 2652 ulong b = 123_456_789_987_654_321uL; 2653 static assert(!__traits(compiles, safeOp.binary!"-="(b, 987_654_321))); 2654 assert(safeOp.binary!"-="(b, 987_654_321u) == 123_456_789_000_000_000uL); 2655 assert(b == 123_456_789_000_000_000uL); 2656 2657 assert(safeOp.binary!"*"(-1 << 30, 2) == int.min); 2658 safeOp.binary!"*"(1 << 30, 2); 2659 assert(IntFlags.local.clear() == IntFlag.negOver); 2660 2661 uint c = 1u << 18; 2662 assert(safeOp.binary!"*="(c, 1u << 4) == 1u << 22); 2663 assert(c == 1u << 22); 2664 2665 assert(safeOp.binary!"/"(22, 11) == 2); 2666 assert(!__traits(compiles, safeOp.binary!"/"(-22, 11u))); 2667 safeOp.binary!"/"(0, 0); 2668 assert(IntFlags.local.clear() == IntFlag.div0); 2669 2670 long j = long.min; 2671 safeOp.binary!"/="(j, -1); 2672 assert(IntFlags.local.clear() == IntFlag.posOver); 2673 2674 assert(safeOp.binary!"%"(20u, 7u) == 6u); 2675 static assert(!__traits(compiles, safeOp.binary!"%"(20u, -7))); 2676 safeOp.binary!"%"(20u, 0u); 2677 assert(IntFlags.local.clear() == IntFlag.div0); 2678 2679 short n = 75; 2680 assert(safeOp.binary!"%="(n, -10) == 5); 2681 assert(n == 5); 2682 } 2683 /// 2684 unittest 2685 { 2686 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2687 2688 assert(safeOp.binary!"<<"(-0x80, 2) == -0x200); 2689 safeOp.binary!"<<"(-0x80, -2); 2690 assert(IntFlags.local.clear() == IntFlag.undef); 2691 2692 ubyte a = 0x3u; 2693 safeOp.binary!"<<="(a, 7); 2694 assert(a == 0x80u); 2695 2696 assert(safeOp.binary!">>"(-0xC, 5u) == -0x1); 2697 safeOp.binary!">>"(-0xC, long.max); 2698 assert(IntFlags.local.clear() == IntFlag.undef); 2699 2700 short b = 0x700; 2701 assert(safeOp.binary!">>="(b, 8) == 0x7); 2702 assert(b == 0x7); 2703 2704 assert(safeOp.binary!">>>"(-0x80, 2u) == 0x3FFF_FFE0); 2705 safeOp.binary!">>>"(-0x80, 32); 2706 assert(IntFlags.local.clear() == IntFlag.undef); 2707 2708 int c = 0xFE_DCBA; 2709 assert(safeOp.binary!">>>="(c, 12) == 0xFED); 2710 assert(c == 0xFED); 2711 2712 assert(safeOp.binary!"&"(0x6Fu, 0x4076) == 0x66u); 2713 2714 ubyte d = 0x6Fu; 2715 assert(safeOp.binary!"&="(d, 0x4076) == 0x66u); 2716 assert(d == 0x66u); 2717 2718 assert(safeOp.binary!"|"(0x6F, 0x4076u) == 0x407Fu); 2719 2720 byte e = 0x6F; 2721 assert(safeOp.binary!"|="(e, 0x4076u) == 0x7F); 2722 assert(e == 0x7F); 2723 2724 assert(safeOp.binary!"^"(0x6F, 0x4076) == 0x4019); 2725 2726 int f = 0x6F; 2727 assert(safeOp.binary!"^="(f, 0x4076) == 0x4019); 2728 assert(f == 0x4019); 2729 2730 assert(!IntFlags.local); 2731 } 2732 2733 /** 2734 Equivalent to `left * pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer alternative to 2735 `left << exp` that is still very fast. 2736 2737 Note that (conceptually) rounding occurs $(I after) the `*`, meaning that `mulPow2(left, -exp)` is equivalent to 2738 `divPow2(left, exp)`. 2739 **/ 2740 auto mulPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2741 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 2742 { 2743 return byPow2Impl!("*", NumFromScal!N, NumFromScal!M)(left, exp); 2744 } 2745 /// ditto 2746 auto mulPow2(N, M)(const N left, const M exp) @safe 2747 if (isFixedPoint!N && isFixedPoint!M) 2748 { 2749 return byPow2Impl!("*", policy, NumFromScal!N, NumFromScal!M)(left, exp); 2750 } 2751 /// 2752 unittest 2753 { 2754 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2755 2756 assert(safeOp.mulPow2(-23, 5) == -736); 2757 safeOp.mulPow2(10_000_000, 10); 2758 assert(IntFlags.local.clear() == IntFlag.posOver); 2759 2760 assert(safeOp.mulPow2(65536, -8) == 256); 2761 assert(safeOp.mulPow2(-100, -100) == 0); 2762 } 2763 2764 /** 2765 Equivalent to `left / pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer alternative to 2766 `left >> exp` that is still very fast. 2767 2768 Note that (conceptually) rounding occurs $(I after) the `/`, meaning that `divPow2(left, -exp)` is equivalent to 2769 `mulPow2(left, exp)`. 2770 **/ 2771 auto divPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2772 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 2773 { 2774 return byPow2Impl!("/", NumFromScal!N, NumFromScal!M)(left, exp); 2775 } 2776 /// ditto 2777 auto divPow2(N, M)(const N left, const M exp) @safe 2778 if (isFixedPoint!N && isFixedPoint!M) 2779 { 2780 return byPow2Impl!("/", policy, NumFromScal!N, NumFromScal!M)(left, exp); 2781 } 2782 /// 2783 unittest 2784 { 2785 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2786 2787 assert(safeOp.divPow2(65536, 8) == 256); 2788 assert(safeOp.divPow2(-100, 100) == 0); 2789 assert(safeOp.divPow2(-23, -5) == -736); 2790 2791 safeOp.divPow2(10_000_000, -10); 2792 assert(IntFlags.local.clear() == IntFlag.posOver); 2793 } 2794 2795 /** 2796 Equivalent to `left % pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer alternative to 2797 `left & ((1 << exp) - 1)` that is still very fast. 2798 **/ 2799 auto modPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2800 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 2801 { 2802 return byPow2Impl!("%", NumFromScal!N, NumFromScal!M)(left, exp); 2803 } 2804 /// ditto 2805 auto modPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2806 if (isFixedPoint!N && isFixedPoint!M) 2807 { 2808 return byPow2Impl!("%", IntFlagPolicy.sticky, NumFromScal!N, NumFromScal!M)(left, exp); 2809 } 2810 /// 2811 unittest 2812 { 2813 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2814 2815 assert(safeOp.modPow2( 101, 1) == 1); 2816 assert(safeOp.modPow2( 101, 3) == 5); 2817 assert(safeOp.modPow2(-101, 3) == -5); 2818 2819 assert(safeOp.modPow2(101, -2) == 0); 2820 assert(safeOp.modPow2(101, 1_000) == 101); 2821 } 2822 2823 /** 2824 Raise `base` to the `exp` power. 2825 2826 Errors that may be signalled if neither input is floating-point: 2827 $(UL 2828 $(LI `IntFlag.posOver` or `IntFlag.negOver` if the absolute value of the result is too large to 2829 represent with the return type.) 2830 $(LI `exp < 0`, `IntFlag.undef` is raised because `std.math.pow` would trigger an FPE given the same input.) 2831 ) 2832 **/ 2833 CallType!(std.math.pow, N, M) pow(N, M)(const N base, const M exp) @safe 2834 if (isFixedPoint!N && isFixedPoint!M) 2835 { 2836 alias R = typeof(return); 2837 static assert(!isSigned!N || isSigned!R, 2838 "std.math.pow(" ~ N.stringof ~ ", " ~ M.stringof ~ 2839 ") is unsafe, due to a signed/unsigned mismatch. Use an explicit cast, or switch to smartOp/SmartInt." 2840 ); 2841 2842 auto po = powImpl!(R, Select!(isSigned!M, long, ulong))(base, exp); 2843 static assert(is(typeof(po.num) == R)); 2844 if (exp < 0) 2845 po.flag = IntFlag.undef; 2846 2847 if (!po.flag.isNull) 2848 po.flag.raise!policy(); 2849 return po.num; 2850 } 2851 /// 2852 unittest 2853 { 2854 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2855 2856 assert(safeOp.pow(-10, 3) == -1_000); 2857 static assert(!__traits(compiles, safeOp.pow(16, 4uL))); 2858 safeOp.pow(2, -1); 2859 assert(IntFlags.local.clear() == IntFlag.undef); 2860 2861 safeOp.pow(-3, 27); 2862 assert(IntFlags.local.clear() == IntFlag.negOver); 2863 safeOp.pow(0, -5); 2864 assert(IntFlags.local.clear() == IntFlag.undef); 2865 } 2866 } 2867 private alias safeOp(bool throws) = safeOp!(cast(Flag!"throws")throws); 2868 2869 // conv ///////////////////////////////////////////////// 2870 /** 2871 A wrapper for `std.conv.to()` which uses `checkedint.flags` for error signaling when converting between any combination 2872 of basic scalar types and `checkedint` types. With an appropriate `policy`, this allows 2873 $(LINK2 ./package.html#to, `checkedint.to()`) to be used for numeric conversions in `pure nothrow` code, unlike 2874 `std.conv.to()`. 2875 2876 Conversions involving any other type are simply forwarded to `std.conv.to()`, with no runtime overhead. 2877 **/ 2878 template to(T, IntFlagPolicy policy) 2879 { 2880 private enum useFlags(S) = (isCheckedInt!T || isScalarType!T) && (isCheckedInt!S || isScalarType!S); 2881 private enum reqAttrs = 2882 ((policy == IntFlagPolicy.sticky || policy == IntFlagPolicy.asserts)? " nothrow" : "") ~ 2883 ((policy == IntFlagPolicy.asserts || policy == IntFlagPolicy.throws)? " pure" : ""); 2884 2885 T to(S)(const S value) @safe 2886 if (useFlags!S) 2887 { 2888 static if (isCheckedInt!T || isCheckedInt!S) 2889 return T(checkedint.to!(BasicScalar!T, policy)(value.bscal)); 2890 else 2891 { 2892 static if (policy != IntFlagPolicy.none && !isFloatingPoint!T) 2893 { 2894 static if (isFloatingPoint!S) 2895 { 2896 if (value >= trueMin!T) 2897 { 2898 if (value > trueMax!T) 2899 IntFlag.posOver.raise!policy(); 2900 } 2901 else 2902 (std.math.isNaN(value)? IntFlag.undef : IntFlag.negOver).raise!policy(); 2903 } 2904 else 2905 { 2906 static if (cast(long)trueMin!S < cast(long)trueMin!T) 2907 { 2908 if (value < cast(S)trueMin!T) 2909 IntFlag.negOver.raise!policy(); 2910 } 2911 static if (cast(ulong)trueMax!S > cast(ulong)trueMax!T) 2912 { 2913 if (value > cast(S)trueMax!T) 2914 IntFlag.posOver.raise!policy(); 2915 } 2916 } 2917 } 2918 return cast(T)value; 2919 } 2920 } 2921 2922 mixin(` 2923 T to(S)(S value)` ~ reqAttrs ~ ` 2924 if (!useFlags!S) 2925 { 2926 import std.conv : impl = to; 2927 return impl!T(value); 2928 }`); 2929 } 2930 /// 2931 unittest 2932 { 2933 // Conversions involving only basic scalars or checkedint types use IntFlags for error signalling. 2934 import checkedint.sticky : smartInt, SmartInt, smartOp, to; // use IntFlagPolicy.sticky 2935 2936 assert(to!int(smartInt(-421751L)) == -421751); 2937 assert(to!(SmartInt!ubyte)(100) == 100u); 2938 2939 assert(is(typeof(to!int(50u)) == int)); 2940 assert(to!int(50u) == 50); 2941 assert(!IntFlags.local); 2942 2943 // If IntFlagPolicy.sticky is set, failed conversions return garbage, but... 2944 assert(smartOp.cmp!"!="(to!int(uint.max), uint.max)); 2945 // ...IntFlags.local can be checked to see if anything went wrong. 2946 assert(IntFlags.local.clear() == IntFlag.posOver); 2947 } 2948 /// 2949 unittest 2950 { 2951 // Everything else forwards to std.conv.to(). 2952 assert(to!(string, IntFlagPolicy.throws)(55) == "55"); 2953 assert(to!(real, IntFlagPolicy.throws)("3.141519e0") == 3.141519L); 2954 2955 // Setting IntFlagPolicy.sticky or .asserts will block std.conv.to(), unless the instantiation is nothrow. 2956 // Setting IntFlagPolicy.asserts or .throws will block std.conv.to(), unless the instantiation is pure. 2957 static assert(!__traits(compiles, to!(real, IntFlagPolicy.sticky)("3.141519e0"))); 2958 } 2959 2960 @property { 2961 /** 2962 Get a view or copy of `num` as a basic scalar. 2963 2964 Useful in generic code that handles both basic types, and `checkedint` types. 2965 **/ 2966 ref inout(N) bscal(N)(return ref inout(N) num) @safe 2967 if (isScalarType!N) 2968 { 2969 return num; 2970 } 2971 /// ditto 2972 ref inout(N) bscal(N)(return ref inout(N) num) @safe 2973 if (isCheckedInt!N) 2974 { 2975 return num.bscal; 2976 } 2977 /// ditto 2978 N bscal(N)(const N num) @safe 2979 if (isScalarType!N) 2980 { 2981 return num; 2982 } 2983 /// ditto 2984 BasicScalar!N bscal(N)(const N num) @safe 2985 if (isCheckedInt!N) 2986 { 2987 return num.bscal; 2988 } 2989 /// 2990 unittest 2991 { 2992 import checkedint.throws : smartInt, SmartInt; // use IntFlagPolicy.throws 2993 2994 assert(is(typeof(bscal(2u)) == uint)); 2995 assert(is(typeof(bscal(SmartInt!int(2))) == int)); 2996 2997 assert(bscal(-3153) == -3153); 2998 assert(bscal(smartInt(75_000)) == 75_000); 2999 } 3000 3001 /** 3002 Get a view or copy of `num` that supports bitwise operations. 3003 3004 Useful in generic code that handles both basic types and `checkedint` types. 3005 **/ 3006 ref inout(N) bits(N)(return ref inout(N) num) @safe 3007 if (isFixedPoint!N) 3008 { 3009 return num; 3010 } 3011 /// ditto 3012 ref inout(SmartInt!(BasicScalar!N, N.policy, Yes.bitOps)) bits(N)(return ref inout(N) num) @safe 3013 if (isSmartInt!N) 3014 { 3015 return num.bits; 3016 } 3017 /// ditto 3018 ref inout(SafeInt!(BasicScalar!N, N.policy, Yes.bitOps)) bits(N)(return ref inout(N) num) @safe 3019 if (isSafeInt!N) 3020 { 3021 return num.bits; 3022 } 3023 /// ditto 3024 N bits(N)(const N num) @safe 3025 if (isFixedPoint!N) 3026 { 3027 return num; 3028 } 3029 /// ditto 3030 SmartInt!(BasicScalar!N, N.policy, Yes.bitOps) bits(N)(const N num) @safe 3031 if (isSmartInt!N) 3032 { 3033 return num.bits; 3034 } 3035 /// ditto 3036 SafeInt!(BasicScalar!N, N.policy, Yes.bitOps) bits(N)(const N num) @safe 3037 if (isSafeInt!N) 3038 { 3039 return num.bits; 3040 } 3041 /// 3042 unittest 3043 { 3044 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 3045 3046 assert(is(typeof(bits(5)) == int)); 3047 3048 SmartInt!(int, No.bitOps) noBits = 5; 3049 assert(is(typeof(bits(noBits)) == SmartInt!(int, Yes.bitOps))); 3050 3051 static assert(!__traits(compiles, noBits << 2)); 3052 assert((bits(noBits) << 2) == 20); 3053 } 3054 3055 /** 3056 Cast `num` to a basic type suitable for indexing an array. 3057 3058 For signed types, `ptrdiff_t` is returned. For unsigned types, `size_t` is returned. 3059 **/ 3060 Select!(isSigned!N, ptrdiff_t, size_t) idx(IntFlagPolicy policy, N)(const N num) @safe 3061 if (isScalarType!N || isCheckedInt!N) 3062 { 3063 return to!(typeof(return), policy)(num.bscal); 3064 } 3065 /// ditto 3066 Select!(isSigned!(BasicScalar!N), ptrdiff_t, size_t) idx(N)(const N num) @safe 3067 if (isCheckedInt!N) 3068 { 3069 return num.idx; 3070 } 3071 /// 3072 unittest 3073 { 3074 import checkedint.sticky : idx, SmartInt, safeInt; // use IntFlagPolicy.sticky 3075 3076 assert(is(typeof(idx(cast(long)1)) == ptrdiff_t)); 3077 assert(is(typeof(idx(cast(ubyte)1)) == size_t)); 3078 assert(is(typeof(idx(SmartInt!ulong(1))) == size_t)); 3079 3080 assert(idx(17uL) == 17); 3081 assert(idx(-3) == -3); 3082 assert(idx(safeInt(cast(byte)100)) == 100); 3083 3084 static if (size_t.sizeof == 4) 3085 { 3086 idx(ulong.max); 3087 assert(IntFlags.local.clear() == IntFlag.posOver); 3088 3089 idx(long.min); 3090 assert(IntFlags.local.clear() == IntFlag.negOver); 3091 } 3092 } 3093 } 3094 /+}+/ 3095 3096 // traits ///////////////////////////////////////////////// 3097 3098 /// Evaluates to `true` if `T` is an instance of $(LINK2 ./package.html#SafeInt, `SafeInt`). 3099 enum isSafeInt(T) = isInstanceOf!(SafeInt, T); 3100 /// 3101 unittest 3102 { 3103 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3104 3105 assert( isSafeInt!(SafeInt!int)); 3106 3107 assert(!isSafeInt!int); 3108 assert(!isSafeInt!(SmartInt!int)); 3109 } 3110 3111 /// Evaluates to `true` if `T` is an instance of $(LINK2 ./package.html#SmartInt, `SmartInt`). 3112 enum isSmartInt(T) = isInstanceOf!(SmartInt, T); 3113 /// 3114 unittest 3115 { 3116 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3117 3118 assert( isSmartInt!(SmartInt!int)); 3119 3120 assert(!isSmartInt!int); 3121 assert(!isSmartInt!(SafeInt!int)); 3122 } 3123 3124 /** 3125 Evaluates to `true` if `T` is an instance of $(LINK2 ./package.html#SafeInt, `SafeInt`) 3126 or $(LINK2 ./package.html#SmartInt, `SmartInt`). 3127 **/ 3128 enum isCheckedInt(T) = isSafeInt!T || isSmartInt!T; 3129 /// 3130 unittest 3131 { 3132 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3133 3134 assert( isCheckedInt!(SafeInt!int)); 3135 assert( isCheckedInt!(SmartInt!int)); 3136 3137 assert(!isCheckedInt!int); 3138 } 3139 3140 /** 3141 Evaluates to `true` if either: 3142 $(UL 3143 $(LI `isScalarType!T`, or) 3144 $(LI `isCheckedInt!T`) 3145 ) 3146 $(B And) bitwise operators such as `<<` and `~` are available for `T`. 3147 **/ 3148 template hasBitOps(T) 3149 { 3150 static if (isCheckedInt!T) 3151 enum hasBitOps = TemplateArgsOf!T[2]; 3152 else 3153 enum hasBitOps = isFixedPoint!T; 3154 } 3155 /// 3156 unittest 3157 { 3158 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3159 3160 assert( hasBitOps!(SafeInt!(int, Yes.bitOps))); 3161 assert( hasBitOps!(SmartInt!(int, Yes.bitOps))); 3162 assert( hasBitOps!int); 3163 assert( hasBitOps!bool); 3164 assert( hasBitOps!dchar); 3165 3166 assert(!hasBitOps!(SafeInt!(int, No.bitOps))); 3167 assert(!hasBitOps!(SmartInt!(int, No.bitOps))); 3168 assert(!hasBitOps!float); 3169 } 3170 3171 /** 3172 Aliases to the basic scalar type associated with `T`, assuming either: 3173 $(UL 3174 $(LI `isScalarType!T`, or) 3175 $(LI `isCheckedInt!T`) 3176 ) 3177 Otherwise, `BasicScalar` aliases to `void`. 3178 **/ 3179 template BasicScalar(T) 3180 { 3181 static if (isScalarType!T) 3182 alias BasicScalar = Unqual!T; 3183 else static if (isCheckedInt!T) 3184 alias BasicScalar = TemplateArgsOf!T[0]; 3185 else 3186 alias BasicScalar = void; 3187 } 3188 /// 3189 unittest 3190 { 3191 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3192 3193 assert(is(BasicScalar!(SafeInt!int) == int)); 3194 assert(is(BasicScalar!(SmartInt!ushort) == ushort)); 3195 3196 assert(is(BasicScalar!int == int)); 3197 assert(is(BasicScalar!(const shared real) == real)); 3198 } 3199 3200 // internal ///////////////////////////////////////////////// 3201 private 3202 { 3203 enum N trueMin(N) = mostNegative!N; 3204 template trueMax(N) 3205 if (isScalarType!N) 3206 { 3207 static if (is(Unqual!N == dchar)) 3208 enum N trueMax = cast(N)~cast(Promoted!N)0; 3209 else 3210 enum N trueMax = N.max; 3211 } 3212 3213 template NumFromScal(N) 3214 if (isScalarType!N) 3215 { 3216 static if (isNumeric!N) 3217 alias NumFromScal = N; 3218 else static if (isSomeChar!N) 3219 alias NumFromScal = IntFromChar!N; 3220 else //if (isBoolean!N) 3221 alias NumFromScal = ubyte; 3222 } 3223 3224 pragma(inline, true) 3225 { 3226 int bsfImpl(IntFlagPolicy policy, N)(const N num) @safe 3227 if (isFixedPoint!N) 3228 { 3229 static if (isSigned!N) 3230 return bsfImpl!(policy, Unsigned!N)(num); 3231 else 3232 { 3233 static assert(N.sizeof <= ulong.sizeof); 3234 3235 int ret = void; 3236 if (num == 0) 3237 { 3238 IntFlag.undef.raise!policy(); 3239 ret = int.min; 3240 } 3241 else 3242 ret = bsf(num); 3243 3244 return ret; 3245 } 3246 } 3247 int bsrImpl(IntFlagPolicy policy, N)(const N num) @safe 3248 if (isFixedPoint!N) 3249 { 3250 static if (isSigned!N) 3251 return bsrImpl!(policy, Unsigned!N)(num); 3252 else 3253 { 3254 static assert(N.sizeof <= ulong.sizeof); 3255 3256 int ret = void; 3257 if (num == 0) 3258 { 3259 IntFlag.undef.raise!policy(); 3260 ret = int.min; 3261 } 3262 else 3263 ret = bsr(num); 3264 3265 return ret; 3266 } 3267 } 3268 3269 auto byPow2Impl(string op, N, M)(const N left, const M exp) pure @safe nothrow @nogc 3270 if (op.among!("*", "/", "%") && ((isFloatingPoint!N && isNumeric!M) || (isNumeric!N && isFloatingPoint!M))) 3271 { 3272 import std.math : exp2, isFinite, frexp, ldexp; 3273 3274 enum wantPrec = max(precision!N, precision!M); 3275 alias R = 3276 Select!(wantPrec <= precision!float, float, 3277 Select!(wantPrec <= precision!double, double, real)); 3278 3279 static if (isFloatingPoint!M) 3280 { 3281 R ret = void; 3282 3283 static if (op.among!("*", "/")) 3284 { 3285 if (left == 0 && exp.isFinite) 3286 ret = 0; 3287 else 3288 { 3289 R wexp = cast(R)exp; 3290 static if (op == "/") 3291 wexp = -wexp; 3292 3293 ret = cast(R)left * exp2(wexp); 3294 } 3295 } 3296 else 3297 { 3298 const p2 = exp2(cast(R)exp); 3299 ret = 3300 p2.isFinite? cast(R)left % p2 : 3301 (p2 > 0)? cast(R)left : 3302 (p2 < 0)? cast(R)0 : 3303 R.nan; 3304 } 3305 3306 return ret; 3307 } 3308 else 3309 { 3310 static if (op.among!("*", "/")) 3311 { 3312 int wexp = 3313 (exp > int.max)? int.max : 3314 (cast(long)exp < -int.max)? -int.max : cast(int)exp; 3315 static if (op == "/") 3316 wexp = -wexp; 3317 3318 return ldexp(cast(R)left, wexp); 3319 } 3320 else 3321 { 3322 int expL; 3323 real mantL = frexp(left, expL); 3324 3325 static if (!isSigned!M) 3326 const retL = expL <= exp; 3327 else 3328 const retL = (expL < 0) || (expL <= exp); 3329 3330 R ret = void; 3331 if (retL) 3332 ret = left; 3333 else 3334 { 3335 const expDiff = expL - exp; 3336 ret = (expDiff > N.mant_dig)? 3337 cast(R)0 : 3338 left - ldexp(floor(ldexp(mantissa, expDiff)), expL - expDiff); 3339 } 3340 3341 return ret; 3342 } 3343 } 3344 } 3345 auto byPow2Impl(string op, IntFlagPolicy policy, N, M)(const N left, const M exp) @safe 3346 if (op.among!("*", "/", "%") && isIntegral!N && isIntegral!M) 3347 { 3348 alias R = Select!(op.among!("*", "/") != 0, Promoted!N, N); 3349 enum Unsigned!M maxSh = 8 * N.sizeof - 1; 3350 3351 R ret = void; 3352 static if (op.among!("*", "/")) 3353 { 3354 const rc = cast(R)left; 3355 const negE = exp < 0; 3356 const absE = cast(Unsigned!M)(negE? 3357 -cast(Promoted!M)exp : 3358 exp); 3359 const bigSh = (absE > maxSh); 3360 3361 R back = void; 3362 if ((op == "*")? negE : !negE) 3363 { 3364 if (bigSh) 3365 ret = 0; 3366 else 3367 { 3368 // ">>" rounds as floor(), but we want trunc() like "/" 3369 ret = (rc < 0)? 3370 -(-rc >>> absE) : 3371 rc >>> absE; 3372 } 3373 } 3374 else 3375 { 3376 if (bigSh) 3377 { 3378 ret = 0; 3379 back = 0; 3380 } 3381 else 3382 { 3383 ret = rc << absE; 3384 back = ret >> absE; 3385 } 3386 3387 if (back != rc) 3388 IntFlag.over.raise!policy(); 3389 } 3390 } 3391 else 3392 { 3393 if (exp & ~cast(Promoted!M)maxSh) 3394 ret = (exp < 0)? 0 : left; 3395 else 3396 { 3397 const mask = ~(~cast(Promoted!N)0 << exp); 3398 ret = cast(R)(left < 0? 3399 -(-cast(Promoted!N)left & mask) : 3400 left & mask); 3401 } 3402 } 3403 3404 return ret; 3405 } 3406 } 3407 3408 struct PowOut(B) 3409 { 3410 B num; 3411 IntFlag flag; 3412 } 3413 3414 // Minimize template bloat by using a common pow() implementation 3415 PowOut!B powImpl(B, E)(const B base, const E exp) @safe 3416 if ((is(B == int) || is(B == uint) || is(B == long) || is(B == ulong)) && 3417 (is(E == long) || is(E == ulong))) 3418 { 3419 pragma(inline, false); 3420 PowOut!B ret; 3421 3422 static if (isSigned!B) 3423 { 3424 alias cmul = muls; 3425 const smallB = (1 >= base && base >= -1); 3426 } 3427 else 3428 { 3429 alias cmul = mulu; 3430 const smallB = (base <= 1); 3431 } 3432 3433 if (smallB) 3434 { 3435 if (base == 0) 3436 { 3437 static if (isSigned!E) 3438 { 3439 if (exp < 0) 3440 ret.flag = IntFlag.div0; 3441 } 3442 3443 ret.num = (exp == 0); 3444 } 3445 else 3446 ret.num = (exp & 0x1)? base : 1; 3447 3448 return ret; 3449 } 3450 if (exp <= 0) 3451 { 3452 ret.num = (exp == 0); 3453 return ret; 3454 } 3455 3456 ret.num = 1; 3457 if (exp <= precision!B) 3458 { 3459 B b = base; 3460 int e = cast(int)exp; 3461 if (e & 0x1) 3462 ret.num = b; 3463 e >>>= 1; 3464 3465 bool over = false; 3466 while (e != 0) 3467 { 3468 b = cmul(b, b, over); 3469 if (e & 0x1) 3470 ret.num = cmul(ret.num, b, over); 3471 3472 e >>>= 1; 3473 } 3474 3475 if (!over) 3476 return ret; 3477 } 3478 3479 ret.flag = (base < 0 && (exp & 0x1))? 3480 IntFlag.negOver : 3481 IntFlag.posOver; 3482 return ret; 3483 } 3484 }