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 import future.bitop, core.checkedint, std.algorithm, std.format, std.meta, future.traits0, std.typecons; 114 static import std.math; 115 116 /+pragma(inline, true) 117 {+/ 118 // smart ///////////////////////////////////////////////// 119 /** 120 Wrapper for any basic integral type `N` that uses the checked operations from 121 $(LINK2 ./package.html#smartOp, `smartOp`) and bounds checks assignments with 122 $(LINK2 ./package.html#to, `checkedint.to()`). 123 124 $(UL 125 $(LI `policy` controls the error signalling policy (see `checkedint.flags`).) 126 $(LI `bitOps` may be set to `No.bitOps` if desired, to turn bitwise operations on this type into a 127 compile-time error.) 128 ) 129 **/ 130 struct SmartInt(N, IntFlagPolicy _policy, Flag!"bitOps" bitOps = Yes.bitOps) 131 if (isIntegral!N && isUnqual!N) 132 { 133 /// The error signalling policy used by this `SmartInt` type. 134 enum IntFlagPolicy policy = _policy; 135 136 static if (bitOps) 137 { 138 /** 139 The basic scalar value of this `SmartInt`. Accessing this directly may be useful for: 140 $(UL 141 $(LI Intentionally doing modular (unchecked) arithmetic, or) 142 $(LI Interacting with APIs that are not `checkedint` aware.) 143 ) 144 **/ 145 N bscal; 146 /// 147 unittest 148 { 149 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 150 151 SmartInt!uint n; 152 static assert(is(typeof(n.bscal) == uint)); 153 154 n = 7; 155 assert(n.bscal == 7); 156 157 n.bscal -= 8; 158 assert(n == uint.max); 159 } 160 161 /// Get a view of this `SmartInt` that allows bitwise operations. 162 @property ref inout(SmartInt!(N, policy, Yes.bitOps)) bits() return inout pure @safe nothrow @nogc 163 { 164 return this; 165 } 166 /// 167 unittest 168 { 169 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 170 171 SmartInt!(int, No.bitOps) n = 1; 172 static assert(!__traits(compiles, n << 2)); 173 assert(n.bits << 2 == 4); 174 } 175 } 176 else 177 { 178 @property ref inout(N) bscal() return inout pure @safe nothrow @nogc 179 { 180 return bits.bscal; 181 } 182 SmartInt!(N, policy, Yes.bitOps) bits; 183 } 184 185 /// The most negative possible value of this `SmartInt` type. 186 enum SmartInt!(N, policy, bitOps) min = typeof(this)(trueMin!N); 187 /// 188 unittest 189 { 190 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 191 192 assert(SmartInt!(int).min == int.min); 193 assert(SmartInt!(uint).min == uint.min); 194 } 195 196 /// The most positive possible value of this `SmartInt` type. 197 enum SmartInt!(N, policy, bitOps) max = typeof(this)(trueMax!N); 198 /// 199 unittest 200 { 201 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws; 202 203 assert(SmartInt!(int).max == int.max); 204 assert(SmartInt!(uint).max == uint.max); 205 } 206 207 // Construction, assignment, and casting ///////////////////////////////////////////////// 208 /** 209 Assign the value of `that` to this `SmartInt` instance. 210 211 $(LINK2 ./package.html#to, `checkedint.to()`) is used to verify `that >= N.min && that <= N.max`. If not, an 212 $(LINK2 ./flags.html, `IntFlag`) will be raised. 213 **/ 214 this(M)(const M that) @safe 215 if (isCheckedInt!M || isScalarType!M) 216 { 217 this.bscal = to!(N, policy)(that); 218 } 219 /// ditto 220 ref typeof(this) opAssign(M)(const M that) @safe 221 if (isCheckedInt!M || isScalarType!M) 222 { 223 this.bscal = to!(N, policy)(that); 224 return this; 225 } 226 /// 227 unittest 228 { 229 import checkedint.sticky : SmartInt; // use IntFlagPolicy.sticky 230 231 // Any basic scalar or checkedint *type* is accepted... 232 SmartInt!int n = 0; 233 n = cast(ulong)0; 234 n = cast(dchar)0; 235 n = cast(byte)0; 236 n = cast(real)0; 237 assert(!IntFlags.local); 238 239 // ...but not any *value*. 240 n = uint.max; 241 n = long.min; 242 n = real.nan; 243 assert(IntFlags.local.clear() == (IntFlag.posOver | IntFlag.negOver | IntFlag.undef)); 244 } 245 246 /** 247 Convert this value to floating-point. This always succeeds, although some loss of precision may 248 occur if M.sizeof <= N.sizeof. 249 **/ 250 M opCast(M)() const pure @safe nothrow @nogc 251 if (isFloatingPoint!M) 252 { 253 return cast(M)bscal; 254 } 255 /// 256 unittest 257 { 258 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 259 260 SmartInt!int n = 92; 261 auto f = cast(double)n; 262 static assert(is(typeof(f) == double)); 263 assert(f == 92.0); 264 } 265 266 /// `this != 0` 267 M opCast(M)() const pure @safe nothrow @nogc 268 if (is(M == bool)) 269 { 270 return bscal != 0; 271 } 272 /// 273 unittest 274 { 275 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 276 277 SmartInt!int n = -315; 278 assert( cast(bool)n); 279 280 n = 0; 281 assert(!cast(bool)n); 282 } 283 284 /** 285 Convert this value to type `M` using $(LINK2 ./package.html#to, `checkedint.to()`) for bounds checking. An 286 $(LINK2 ./flags.html, `IntFlag`) will be raised if `M` cannot represent the current value of this `SmartInt`. 287 **/ 288 M opCast(M)() const @safe 289 if (isCheckedInt!M || isIntegral!M || isSomeChar!M) 290 { 291 return to!(M, policy)(bscal); 292 } 293 /// 294 unittest 295 { 296 import checkedint.sticky : SmartInt; // use IntFlagPolicy.sticky 297 298 SmartInt!ulong n = 52; 299 auto a = cast(int)n; 300 static assert(is(typeof(a) == int)); 301 assert(!IntFlags.local); 302 assert(a == 52); 303 304 auto m = SmartInt!long(-1).mulPow2(n); 305 auto b = cast(wchar)m; 306 static assert(is(typeof(b) == wchar)); 307 assert(IntFlags.local.clear() == IntFlag.negOver); 308 } 309 310 /** 311 Convert this value to a type suitable for indexing an array: 312 $(UL 313 $(LI If `N` is signed, a `ptrdiff_t` is returned.) 314 $(LI If `N` is unsigned, a `size_t` is returned.) 315 ) 316 $(LINK2 ./package.html#to, `checkedint.to()`) is used for bounds checking. 317 **/ 318 @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const @safe 319 { 320 return to!(typeof(return), policy)(bscal); 321 } 322 /// 323 unittest 324 { 325 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 326 327 char[3] arr = ['a', 'b', 'c']; 328 SmartInt!long n = 1; 329 330 // On 32-bit, `long` cannot be used directly for array indexing, 331 static if (size_t.sizeof < long.sizeof) 332 static assert(!__traits(compiles, arr[n])); 333 // but idx can be used to concisely and safely cast to ptrdiff_t: 334 assert(arr[n.idx] == 'b'); 335 336 // The conversion is bounds checked: 337 static if (size_t.sizeof < long.sizeof) 338 { 339 n = long.min; 340 try 341 { 342 arr[n.idx] = '?'; 343 } 344 catch (CheckedIntException e) 345 { 346 assert(e.intFlags == IntFlag.negOver); 347 } 348 } 349 } 350 351 /// Get a simple hashcode for this value. 352 size_t toHash() const pure @safe nothrow @nogc 353 { 354 static if (N.sizeof > size_t.sizeof) 355 { 356 static assert(N.sizeof == (2 * size_t.sizeof)); 357 return cast(size_t)bscal ^ cast(size_t)(bscal >>> 32); 358 } 359 else 360 return cast(size_t)bscal; 361 } 362 363 /// Get a `string` representation of this value. 364 string toString() const @safe 365 { 366 return to!(string, IntFlagPolicy.sticky)(bscal); 367 } 368 /// 369 unittest 370 { 371 import checkedint.throws : smartInt; // use IntFlagPolicy.throws 372 assert(smartInt(-753).toString() == "-753"); 373 } 374 /** 375 Puts a `string` representation of this value into `w`. This overload will not allocate, unless 376 `std.range.primitives.put(w, ...)` allocates. 377 378 Params: 379 w = An output range that will receive the `string` 380 fmt = An optional format specifier 381 */ 382 void toString(Writer, Char = char)(Writer w, FormatSpec!Char fmt = (FormatSpec!Char).init) const 383 { 384 formatValue(w, bscal, fmt); 385 } 386 387 // Comparison ///////////////////////////////////////////////// 388 /// Returns `true` if this value is mathematically precisely equal to `right`. 389 bool opEquals(M)(const M right) const pure @safe nothrow @nogc 390 if (isCheckedInt!M || isScalarType!M) 391 { 392 return smartOp!(policy).cmp!"=="(this.bscal, right.bscal); 393 } 394 /** 395 Perform a mathematically correct comparison to `right`. 396 397 Returns: $(UL 398 $(LI `-1` if this value is less than `right`.) 399 $(LI ` 0` if this value is precisely equal to `right`.) 400 $(LI ` 1` if this value is greater than `right`.) 401 $(LI `float.nan` if `right` is a floating-point `nan` value.) 402 ) 403 **/ 404 auto opCmp(M)(const M right) const pure @safe nothrow @nogc 405 if (isFloatingPoint!M) 406 { 407 return 408 (bscal < right)? -1 : 409 (bscal > right)? 1 : 410 (bscal == right)? 0 : float.nan; 411 } 412 /// ditto 413 int opCmp(M)(const M right) const pure @safe nothrow @nogc 414 if (isCheckedInt!M || isScalarType!M) 415 { 416 return smartOp!(policy).cmp(this.bscal, right.bscal); 417 } 418 419 // Unary ///////////////////////////////////////////////// 420 /// See $(LINK2 ./package.html#smartOp, `smartOp`). 421 typeof(this) opUnary(string op)() const pure @safe nothrow @nogc 422 if (op == "~") 423 { 424 static assert(bitOps, 425 "Bitwise operations are disabled."); 426 427 return typeof(return)(smartOp!(policy).unary!op(bscal)); 428 } 429 /// ditto 430 SmartInt!(Signed!N, policy, bitOps) opUnary(string op)() const @safe 431 if (op == "+" || op == "-") 432 { 433 return typeof(return)(smartOp!(policy).unary!op(bscal)); 434 } 435 /// ditto 436 ref typeof(this) opUnary(string op)() return @safe 437 if (op.among!("++", "--")) 438 { 439 smartOp!(policy).unary!op(bscal); 440 return this; 441 } 442 443 /// ditto 444 SmartInt!(Unsigned!N, policy, bitOps) abs() const pure @safe nothrow @nogc 445 { 446 return typeof(return)(smartOp!(policy).abs(bscal)); 447 } 448 449 /// Count the number of set bits using `core.bitop.popcnt()`. 450 SmartInt!(int, policy, bitOps) popcnt()() const pure @safe nothrow @nogc 451 { 452 static assert(bitOps, "Bitwise operations are disabled."); 453 454 import future.bitop : stdPC = popcnt; 455 return typeof(return)(stdPC(bscal)); 456 } 457 458 /// See $(LINK2 ./package.html#smartOp, `smartOp`). 459 SmartInt!(ubyte, policy, bitOps) bsf()() const @safe 460 { 461 static assert(bitOps, "Bitwise operations are disabled."); 462 463 return typeof(return)(smartOp!(policy).bsf(bscal)); 464 } 465 /// ditto 466 SmartInt!(ubyte, policy, bitOps) bsr()() const @safe 467 { 468 static assert(bitOps, "Bitwise operations are disabled. Consider using ilogb() instead?"); 469 470 return typeof(return)(smartOp!(policy).bsr(bscal)); 471 } 472 473 /// ditto 474 SmartInt!(ubyte, policy, bitOps) ilogb() const @safe 475 { 476 return typeof(return)(smartOp!(policy).ilogb(bscal)); 477 } 478 479 // Binary ///////////////////////////////////////////////// 480 /// ditto 481 auto opBinaryRight(string op, M)(const M left) const pure @safe nothrow @nogc 482 if (isFloatingPoint!M) 483 { 484 return smartOp!(policy).binary!op(left, bscal); 485 } 486 /// ditto 487 auto opBinary(string op, M)(const M right) const pure @safe nothrow @nogc 488 if (isFloatingPoint!M) 489 { 490 return smartOp!(policy).binary!op(bscal, right); 491 } 492 /// ditto 493 auto opBinaryRight(string op, M)(const M left) const @safe 494 if (isSafeInt!M || isFixedPoint!M) 495 { 496 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 497 enum mixBitOps = bitOps && hasBitOps!M; 498 static assert(mixBitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 499 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 500 501 const wret = smartOp!(mixPolicy).binary!op(left.bscal, this.bscal); 502 return SmartInt!(typeof(wret), mixPolicy, mixBitOps)(wret); 503 } 504 /// ditto 505 auto opBinary(string op, M)(const M right) const @safe 506 if (isCheckedInt!M || isFixedPoint!M) 507 { 508 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 509 enum mixBitOps = bitOps && hasBitOps!M; 510 static assert(mixBitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 511 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 512 513 const wret = smartOp!(mixPolicy).binary!op(this.bscal, right.bscal); 514 return SmartInt!(typeof(wret), mixPolicy, mixBitOps)(wret); 515 } 516 /// ditto 517 ref typeof(this) opOpAssign(string op, M)(const M right) return @safe 518 if (isCheckedInt!M || isFixedPoint!M) 519 { 520 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 521 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 522 523 smartOp!(.max(policy, intFlagPolicyOf!M)).binary!(op ~ "=")(this.bscal, right.bscal); 524 return this; 525 } 526 527 /// ditto 528 auto mulPow2(M)(const M exp) const pure @safe nothrow @nogc 529 if (isFloatingPoint!M) 530 { 531 return smartOp!(policy).mulPow2(bscal, exp); 532 } 533 /// ditto 534 auto mulPow2(M)(const M exp) const @safe 535 if (isCheckedInt!M || isFixedPoint!M) 536 { 537 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 538 const wret = smartOp!(mixPolicy).mulPow2(this.bscal, exp.bscal); 539 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 540 } 541 /// ditto 542 auto divPow2(M)(const M exp) const pure @safe nothrow @nogc 543 if (isFloatingPoint!M) 544 { 545 return smartOp!(policy).divPow2(bscal, exp); 546 } 547 /// ditto 548 auto divPow2(M)(const M exp) const @safe 549 if (isCheckedInt!M || isFixedPoint!M) 550 { 551 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 552 const wret = smartOp!(mixPolicy).divPow2(this.bscal, exp.bscal); 553 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 554 } 555 /// ditto 556 auto modPow2(M)(const M exp) const pure @safe nothrow @nogc 557 if (isFloatingPoint!M) 558 { 559 return smartOp!(policy).modPow2(bscal, exp); 560 } 561 /// ditto 562 auto modPow2(M)(const M exp) const @safe 563 if (isCheckedInt!M || isFixedPoint!M) 564 { 565 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 566 const wret = smartOp!(mixPolicy).modPow2(this.bscal, exp.bscal); 567 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 568 } 569 570 /// Raise `this` to the `exp` power using `std.math.pow()`. 571 auto pow(M)(const M exp) const pure @safe nothrow @nogc 572 if (isFloatingPoint!M) 573 { 574 return std.math.pow(bscal, exp); 575 } 576 /// See $(LINK2 ./package.html#smartOp, `smartOp`). 577 auto pow(M)(const M exp) const @safe 578 if (isCheckedInt!M || isFixedPoint!M) 579 { 580 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 581 const wret = smartOp!(mixPolicy).pow(this.bscal, exp.bscal); 582 return SmartInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 583 } 584 } 585 /// ditto 586 template SmartInt(N, IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps) 587 if ((isIntegral!N && !isUnqual!N) || isCheckedInt!N) 588 { 589 alias SmartInt = SmartInt!(BasicScalar!N, policy, bitOps); 590 } 591 /// 592 unittest 593 { 594 // Mixing standard signed and unsigned types is dangerous, but... 595 int ba = -1; 596 uint bb = 0; 597 assert(ba > bb); 598 599 auto bc = ba + bb; 600 assert(is(typeof(bc) == uint)); 601 assert(bc == 4294967295u); 602 603 // ...with SmartInt, mixed signed/unsigned operations "just work": 604 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 605 606 SmartInt!int ma = -1; 607 SmartInt!uint mb = 0; 608 assert(ma < mb); 609 610 auto mc = ma + mb; 611 assert(is(typeof(mc) == SmartInt!int)); 612 assert(mc != 4294967295u); 613 assert(mc == -1); 614 } 615 /// 616 unittest 617 { 618 // When IntFlagPolicy.throws is used, failed SmartInt operations will throw a CheckedIntException. 619 import checkedint.throws : SmartInt; 620 621 SmartInt!uint ma = 1; 622 SmartInt!uint mb = 0; 623 624 bool overflow = false; 625 try 626 { 627 SmartInt!uint mc = mb - ma; 628 assert(false); 629 } 630 catch (CheckedIntException e) 631 { 632 assert(e.intFlags == IntFlag.negOver); 633 overflow = true; 634 } 635 assert(overflow); 636 637 bool div0 = false; 638 try 639 { 640 // With standard integers, this would crash the program with an unrecoverable FPE... 641 SmartInt!uint mc = ma / mb; 642 assert(false); 643 } 644 catch (CheckedIntException e) 645 { 646 // ...but with SmartInt, it just throws a normal Exception. 647 assert(e.intFlags == IntFlag.div0); 648 div0 = true; 649 } 650 assert(div0); 651 } 652 /// 653 unittest 654 { 655 // When IntFlagPolicy.sticky is used, failed SmartInt operations set one or more bits in IntFlags.local. 656 import checkedint.sticky : SmartInt; 657 658 SmartInt!uint ma = 1; 659 SmartInt!uint mb = 0; 660 SmartInt!uint mc; 661 662 mc = mb - ma; 663 assert(IntFlags.local == IntFlag.negOver); 664 665 // With standard integers, this would crash the program with an unrecoverable FPE... 666 mc = ma / mb; 667 // ...but with SmartInt, it just sets a bit in IntFlags.local. 668 assert(IntFlags.local & IntFlag.div0); 669 670 // Each flag will remain set until cleared: 671 assert(IntFlags.local.clear() == (IntFlag.negOver | IntFlag.div0)); 672 assert(!IntFlags.local); 673 } 674 675 private template SmartInt(N, IntFlagPolicy policy, bool bitOps) 676 if (isIntegral!N) 677 { 678 alias SmartInt = SmartInt!( 679 Unqual!N, 680 policy, 681 cast(Flag!"bitOps")bitOps); 682 } 683 684 /// Get the value of `num` as a `SmartInt!N`. The integral type `N` can be infered from the argument. 685 SmartInt!(N, policy, bitOps) smartInt(IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps, N)(N num) @safe 686 if (isIntegral!N || isCheckedInt!N) 687 { 688 return typeof(return)(num.bscal); 689 } 690 /// 691 unittest 692 { 693 import checkedint.throws : smartInt, SmartInt; // use IntFlagPolicy.throws 694 695 auto a = smartInt(55uL); 696 static assert(is(typeof(a) == SmartInt!ulong)); 697 assert(a == 55); 698 } 699 700 /** 701 Implements various integer math operations with error checking. 702 703 `smartOp` strives to give the mathematically correct result, with integer-style rounding, for all inputs. Only 704 if the correct result is undefined or not representable by the return type is an error signalled, using 705 `checkedint.flags`. 706 707 The error-signalling policy may be selected using the `policy` template parameter. 708 **/ 709 template smartOp(IntFlagPolicy policy) 710 { 711 // NOTE: ddoc only scans the first branch of a static if 712 static if (policy == IntFlagPolicy.none) 713 { 714 // No need to redundantly instantiate members which don't depend on `policy`. 715 716 private void cmpTypeCheck(N, M)() pure nothrow @nogc 717 { 718 static assert(isBoolean!N == isBoolean!M, 719 "The intent of a direct comparison of " ~ 720 N.stringof ~ " with " ~ M.stringof ~ 721 " is unclear. Add an explicit cast." 722 ); 723 } 724 725 /** 726 Compare `left` and `right` using `op`. 727 $(UL 728 $(LI Unlike the standard integer comparison operator, this function correctly handles negative 729 values in signed/unsigned comparisons.) 730 $(LI Like the standard operator, comparisons involving any floating-point `nan` value always return 731 `false`.) 732 ) $(BR) 733 Direct comparisons between boolean values and numeric ones are forbidden. Make the intent explicit: 734 $(UL 735 $(LI `numeric == cast(N)boolean`) 736 $(LI `(numeric != 0) == boolean`) 737 ) 738 **/ 739 bool cmp(string op, N, M)(const N left, const M right) pure @safe nothrow @nogc 740 if (isScalarType!N && isScalarType!M) 741 { 742 cmpTypeCheck!(N, M)(); 743 744 static if (isSigned!N != isSigned!M) 745 { 746 static if (isSigned!N) 747 { 748 if (left < 0) 749 return mixin("-1 " ~ op ~ " 0"); 750 } 751 else 752 { 753 if (right < 0) 754 return mixin("0 " ~ op ~ " -1"); 755 } 756 } 757 758 return mixin("left " ~ op ~ " right"); 759 } 760 /// 761 unittest 762 { 763 import checkedint.sticky : smartOp; // smartOp.cmp() never throws 764 765 assert(uint.max == -1); 766 assert( smartOp.cmp!"!="(uint.max, -1)); 767 assert(-3156 > 300u); 768 assert( smartOp.cmp!"<"(-3156, 300u)); 769 770 assert(!smartOp.cmp!"<"(1, real.nan)); 771 assert(!smartOp.cmp!"<"(real.nan, 1)); 772 } 773 774 /** 775 Defines a total order on all basic scalar values, using the same rules as `std.math.cmp()`. 776 777 $(UL 778 $(LI Mixed signed/unsigned comparisons return the mathematically correct result.) 779 $(LI If neither `left` nor `right` is floating-point, this function is faster than 780 `std.math.cmp()`.) 781 $(LI If either `left` or `right` $(I is) floating-point, this function forwards to 782 `std.math.cmp()`.) 783 ) $(BR) 784 Direct comparisons between boolean values and numeric ones are forbidden. Make the intent explicit: 785 $(UL 786 $(LI `numeric == cast(N)boolean`) 787 $(LI `(numeric != 0) == boolean`) 788 ) 789 **/ 790 int cmp(N, M)(const N left, const M right) pure @safe nothrow @nogc 791 if (isScalarType!N && isScalarType!M) 792 { 793 cmpTypeCheck!(N, M)(); 794 795 static if (isFloatingPoint!N || isFloatingPoint!M) 796 { 797 import future.math : stdCmp = cmp; 798 return stdCmp(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 -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 ~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 ~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)? -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 = negG? 1302 -cast(UM)right : 1303 cast(UM)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 typeof(this) 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 typeof(this) 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 import future.bitop : stdPC = popcnt; 2003 return typeof(return)(stdPC(bscal)); 2004 } 2005 2006 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 2007 SafeInt!(int, policy, bitOps) bsf()() const @safe 2008 { 2009 static assert(bitOps, "Bitwise operations are disabled."); 2010 2011 return typeof(return)(safeOp!(policy).bsf(bscal)); 2012 } 2013 /// ditto 2014 SafeInt!(int, policy, bitOps) bsr()() const @safe 2015 { 2016 static assert(bitOps, "Bitwise operations are disabled. Consider using ilogb() instead?"); 2017 2018 return typeof(return)(safeOp!(policy).bsr(bscal)); 2019 } 2020 2021 /// ditto 2022 SafeInt!(int, policy, bitOps) ilogb() const @safe 2023 { 2024 return typeof(return)(safeOp!(policy).ilogb(bscal)); 2025 } 2026 2027 // Binary ///////////////////////////////////////////////// 2028 /// Perform a floating-point math operation. 2029 M opBinaryRight(string op, M)(const M left) const pure @safe nothrow @nogc 2030 if (isFloatingPoint!M) 2031 { 2032 return mixin("left " ~ op ~ " bscal"); 2033 } 2034 /// ditto 2035 M opBinary(string op, M)(const M right) const pure @safe nothrow @nogc 2036 if (isFloatingPoint!M) 2037 { 2038 return mixin("bscal " ~ op ~ " right"); 2039 } 2040 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 2041 SafeInt!(OpType!(M, op, N), policy, bitOps) opBinaryRight(string op, M)(const M left) const @safe 2042 if (isFixedPoint!M) 2043 { 2044 static assert(bitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 2045 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 2046 2047 return typeof(return)(safeOp!(policy).binary!op(left, bscal)); 2048 } 2049 /// ditto 2050 SafeInt!(OpType!(N, op, BasicScalar!M), .max(policy, intFlagPolicyOf!M), bitOps && hasBitOps!M) opBinary(string op, M)(const M right) const @safe 2051 if (isSafeInt!M || isFixedPoint!M) 2052 { 2053 static assert(bitOps && hasBitOps!M || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 2054 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 2055 2056 return typeof(return)(safeOp!(.max(policy, intFlagPolicyOf!M)).binary!op(this.bscal, right.bscal)); 2057 } 2058 /// ditto 2059 ref typeof(this) opOpAssign(string op, M)(const M right) return @safe 2060 if (isCheckedInt!M || isFixedPoint!M) 2061 { 2062 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"), 2063 "Bitwise operations are disabled. Consider using mulPow2(), divPow2(), or modPow2() instead?"); 2064 checkImplicit!(OpType!(N, op, BasicScalar!M))(); 2065 2066 safeOp!(.max(policy, intFlagPolicyOf!M)).binary!(op ~ "=")(this.bscal, right.bscal); 2067 return this; 2068 } 2069 2070 /// ditto 2071 auto mulPow2(M)(const M exp) const pure @safe nothrow @nogc 2072 if (isFloatingPoint!M) 2073 { 2074 return safeOp!(policy).mulPow2(bscal, exp); 2075 } 2076 /// ditto 2077 auto mulPow2(M)(const M exp) const @safe 2078 if (isCheckedInt!M || isFixedPoint!M) 2079 { 2080 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 2081 const wret = safeOp!(mixPolicy).mulPow2(this.bscal, exp.bscal); 2082 return SafeInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 2083 } 2084 /// ditto 2085 auto divPow2(M)(const M exp) const pure @safe nothrow @nogc 2086 if (isFloatingPoint!M) 2087 { 2088 return safeOp!(policy).divPow2(bscal, exp); 2089 } 2090 /// ditto 2091 auto divPow2(M)(const M exp) const @safe 2092 if (isCheckedInt!M || isFixedPoint!M) 2093 { 2094 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 2095 const wret = safeOp!(mixPolicy).divPow2(this.bscal, exp.bscal); 2096 return SafeInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 2097 } 2098 /// ditto 2099 auto modPow2(M)(const M exp) const pure @safe nothrow @nogc 2100 if (isFloatingPoint!M) 2101 { 2102 return safeOp!(policy).modPow2(bscal, exp); 2103 } 2104 /// ditto 2105 auto modPow2(M)(const M exp) const @safe 2106 if (isCheckedInt!M || isFixedPoint!M) 2107 { 2108 enum mixPolicy = .max(policy, intFlagPolicyOf!M); 2109 const wret = safeOp!(mixPolicy).modPow2(this.bscal, exp.bscal); 2110 return SafeInt!(typeof(wret), mixPolicy, bitOps && hasBitOps!M)(wret); 2111 } 2112 2113 /// Raise `this` to the `exp` power using `std.math.pow()`. 2114 M pow(M)(const M exp) const pure @safe nothrow @nogc 2115 if (isFloatingPoint!M) 2116 { 2117 return std.math.pow(bscal, exp); 2118 } 2119 /// See $(LINK2 ./package.html#safeOp, `safeOp`). 2120 SafeInt!(CallType!(std.math.pow, N, BasicScalar!M), .max(policy, intFlagPolicyOf!M), bitOps && hasBitOps!M) pow(M)(const M exp) const @safe 2121 if (isCheckedInt!M || isFixedPoint!M) 2122 { 2123 return typeof(return)(safeOp!(.max(policy, intFlagPolicyOf!M)).pow(this.bscal, exp.bscal)); 2124 } 2125 } 2126 /// ditto 2127 template SafeInt(N, IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps) 2128 if ((isIntegral!N && !isUnqual!N) || isCheckedInt!N) 2129 { 2130 alias SafeInt = SafeInt!(BasicScalar!N, policy, bitOps); 2131 } 2132 /// 2133 unittest 2134 { 2135 // Mixing standard signed and unsigned types is dangerous... 2136 int ba = -1; 2137 uint bb = 0; 2138 assert(ba > bb); 2139 2140 auto bc = ba + bb; 2141 assert(is(typeof(bc) == uint)); 2142 assert(bc == 4294967295u); 2143 2144 // ...that's why SafeInt doesn't allow it. 2145 import checkedint.throws : SafeInt, to; // use IntFlagPolicy.throws 2146 2147 SafeInt!int sa = -1; 2148 SafeInt!uint sb = 0u; 2149 static assert(!__traits(compiles, sa < sb)); 2150 static assert(!__traits(compiles, sa + sb)); 2151 2152 // Instead, use checkedint.to() to safely convert to a common type... 2153 auto sbi = to!(SafeInt!int)(sb); 2154 assert(sa < sbi); 2155 auto sc = sa + sbi; 2156 assert(sc == -1); 2157 // (...or just switch to SmartInt.) 2158 } 2159 /// 2160 unittest 2161 { 2162 // When IntFlagPolicy.throws is set, SafeInt operations that fail at runtime will throw a CheckedIntException. 2163 import checkedint.throws : SafeInt; 2164 2165 SafeInt!uint sa = 1u; 2166 SafeInt!uint sb = 0u; 2167 2168 bool overflow = false; 2169 try 2170 { 2171 SafeInt!uint sc = sb - sa; 2172 assert(false); 2173 } 2174 catch (CheckedIntException e) 2175 { 2176 assert(e.intFlags == IntFlag.negOver); 2177 overflow = true; 2178 } 2179 assert(overflow); 2180 2181 bool div0 = false; 2182 try 2183 { 2184 // With standard integers, this would crash the program with an unrecoverable FPE... 2185 SafeInt!uint sc = sa / sb; 2186 assert(false); 2187 } 2188 catch (CheckedIntException e) 2189 { 2190 // ...but with SafeInt, it just throws a normal Exception. 2191 assert(e.intFlags == IntFlag.div0); 2192 div0 = true; 2193 } 2194 assert(div0); 2195 } 2196 /// 2197 unittest 2198 { 2199 // When IntFlagPolicy.sticky is set, SafeInt operations that fail at runtime set one or more bits in IntFlags.local. 2200 import checkedint.sticky : SafeInt; 2201 2202 SafeInt!uint sa = 1u; 2203 SafeInt!uint sb = 0u; 2204 SafeInt!uint sc; 2205 2206 sc = sb - sa; 2207 assert(IntFlags.local == IntFlag.negOver); 2208 2209 // With standard integers, this would crash the program with an unrecoverable FPE... 2210 sc = sa / sb; 2211 // ...but with SmartInt, it just sets a bit in IntFlags.local. 2212 assert(IntFlags.local & IntFlag.div0); 2213 2214 // Each flag will remain set until cleared: 2215 assert(IntFlags.local.clear() == (IntFlag.negOver | IntFlag.div0)); 2216 assert(!IntFlags.local); 2217 } 2218 2219 private template SafeInt(N, IntFlagPolicy policy, bool bitOps) 2220 if (isIntegral!N) 2221 { 2222 alias SafeInt = SafeInt!( 2223 Unqual!N, 2224 policy, 2225 cast(Flag!"bitOps")bitOps); 2226 } 2227 2228 /// Get the value of `num` as a `SafeInt!N`. The integral type `N` can be infered from the argument. 2229 SafeInt!(N, policy, bitOps) safeInt(IntFlagPolicy policy, Flag!"bitOps" bitOps = Yes.bitOps, N)(N num) @safe 2230 if (isIntegral!N || isCheckedInt!N) 2231 { 2232 return typeof(return)(num.bscal); 2233 } 2234 /// 2235 unittest 2236 { 2237 import checkedint.throws : safeInt, SafeInt; // use IntFlagPolicy.throws 2238 2239 auto a = safeInt(55uL); 2240 static assert(is(typeof(a) == SafeInt!ulong)); 2241 assert(a == 55u); 2242 } 2243 2244 /** 2245 Implements various integer math operations with error checking. 2246 2247 `safeOp` strives to mimic the standard integer math operations in every way, except: 2248 $(UL 2249 $(LI If the operation is generally untrustworthy - for example, signed/unsigned comparisons - a compile-time error 2250 is generated. The message will usually suggest a workaround.) 2251 $(LI At runtime, if the result is mathematically incorrect an appropriate $(LINK2 ./flags.html, `IntFlag`) 2252 will be raised.) 2253 ) 2254 The runtime error-signalling policy may be selected using the `policy` template parameter. 2255 **/ 2256 template safeOp(IntFlagPolicy policy) 2257 { 2258 // NOTE: ddoc only scans the first branch of a static if 2259 static if (policy == IntFlagPolicy.none) 2260 { 2261 // No need to redundantly instantiate members which don't depend on `policy`. 2262 2263 private void cmpTypeCheck(N, M)() pure @safe nothrow @nogc 2264 { 2265 static assert(isBoolean!N == isBoolean!M, 2266 "The intent of a direct comparison of " ~ 2267 N.stringof ~ " with " ~ M.stringof ~ 2268 " is unclear. Add an explicit cast." 2269 ); 2270 2271 alias OT = OpType!(N, "+", M); 2272 static assert(isFloatingPoint!OT || isSigned!OT || !(isSigned!N || isSigned!M), 2273 "The standard signed/unsigned comparisons of " ~ N.stringof ~ " to " ~ M.stringof ~ 2274 " are unsafe. Use an explicit cast, or switch to smartOp/SmartInt." 2275 ); 2276 } 2277 2278 /** 2279 Compare `left` and `right` using `op`. 2280 2281 Unsafe signed/unsigned comparisons will trigger a compile-time error. Possible solutions include: 2282 $(UL 2283 $(LI Should the inputs really have different signedness? Changing the type of one to match the other 2284 is the simplest solution.) 2285 $(LI Consider using `smartOp.cmp()`, instead, as it can safely do signed/unsigned comparisons.) 2286 $(LI Alternately, $(LINK2 ./package.html#to, `checkedint.to()`) can be used to safely convert the 2287 type of one input, with runtime bounds checking.) 2288 ) $(BR) 2289 Direct comparisons between boolean values and numeric ones are also forbidden. Make the intent explicit: 2290 $(UL 2291 $(LI `numeric == cast(N)boolean`) 2292 $(LI `(numeric != 0) == boolean`) 2293 ) 2294 **/ 2295 bool cmp(string op, N, M)(const N left, const M right) pure @safe nothrow @nogc 2296 if (isScalarType!N && isScalarType!M) 2297 { 2298 cmpTypeCheck!(N, M)(); 2299 return mixin("left " ~ op ~ " right"); 2300 } 2301 /// 2302 unittest 2303 { 2304 import checkedint.sticky : safeOp; // safeOp.cmp() never throws 2305 2306 assert(safeOp.cmp!"=="(int.max, 0x7FFF_FFFF)); 2307 assert(safeOp.cmp!"!="(uint.min, 5u)); 2308 assert(safeOp.cmp!"<="(int.min, 0)); 2309 2310 static assert(!__traits(compiles, safeOp.cmp!"=="(uint.max, -1))); 2311 static assert(!__traits(compiles, safeOp.cmp!">"(-1, 1u))); 2312 } 2313 } 2314 else 2315 alias cmp = safeOp!(IntFlagPolicy.none).cmp; 2316 2317 /** 2318 Perform the unary (single-argument) integer operation specified by `op`. 2319 2320 Trying to negate `-` an unsigned value will generate a compile-time error, because mathematically, the result should 2321 always be negative (except for -0), but the unsigned return type cannot represent this. 2322 2323 `++` and `--` are checked for overflow at runtime, and will raise `IntFlag.posOver` or `IntFlag.negOver` if needed. 2324 **/ 2325 N unary(string op, N)(const N num) @safe 2326 if ((isIntegral!N) && op.among!("-", "+", "~")) 2327 { 2328 static assert(isSigned!N || op != "-", 2329 "The standard unary - operation for " ~ N.stringof ~ 2330 " is unsafe. Use an explicit cast to a signed type, or switch to smartOp/SmartInt." 2331 ); 2332 2333 static if (op == "-") 2334 { 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 N ret = -num; 2344 } 2345 2346 if (over) 2347 IntFlag.posOver.raise!policy(); 2348 2349 return ret; 2350 } 2351 else 2352 return mixin(op ~ "num"); 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 N abs(N)(const N num) @safe 2404 if (isIntegral!N || isBoolean!N) 2405 { 2406 static if (isSigned!N) 2407 { 2408 if (num < 0) 2409 return unary!"-"(num); 2410 } 2411 return num; 2412 } 2413 /// 2414 unittest 2415 { 2416 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2417 2418 assert(safeOp.abs(-25) == 25); 2419 assert(safeOp.abs(745u) == 745u); 2420 2421 safeOp.abs(int.min); 2422 assert(IntFlags.local.clear() == IntFlag.posOver); 2423 } 2424 2425 /// `core.bitop.bsf` without the undefined behaviour. `safeOp.bsf(0)` will raise `IntFlag.undef`. 2426 int bsf(N)(const N num) @safe 2427 if (isFixedPoint!N) 2428 { 2429 return bsfImpl!policy(num); 2430 } 2431 /// 2432 unittest 2433 { 2434 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2435 2436 assert(safeOp.bsf(20) == 2); 2437 2438 safeOp.bsf(0); 2439 assert(IntFlags.local.clear() == IntFlag.undef); 2440 } 2441 2442 /// `core.bitop.bsr` without the undefined behaviour. `safeOp.bsr(0)` will raise `IntFlag.undef`. 2443 int bsr(N)(const N num) @safe 2444 if (isFixedPoint!N) 2445 { 2446 return bsrImpl!policy(num); 2447 } 2448 /// 2449 unittest 2450 { 2451 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2452 2453 assert(safeOp.bsr( 20) == 4); 2454 assert(safeOp.bsr(-20) == 31); 2455 2456 safeOp.bsr(0); 2457 assert(IntFlags.local.clear() == IntFlag.undef); 2458 } 2459 2460 /** 2461 Get the base 2 logarithm of `abs(num)`, rounded down to the nearest integer. 2462 2463 `safeOp.ilogb(0)` will raise `IntFlag.undef`. 2464 **/ 2465 int ilogb(N)(const N num) @safe 2466 if (isFixedPoint!N) 2467 { 2468 static if (isSigned!N) 2469 const absN = cast(Unsigned!N) (num < 0? -num : num); 2470 else 2471 alias absN = num; 2472 2473 return bsrImpl!policy(absN); 2474 } 2475 /// 2476 unittest 2477 { 2478 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2479 2480 assert(safeOp.ilogb( 20) == 4); 2481 assert(safeOp.ilogb(-20) == 4); 2482 2483 safeOp.ilogb(0); 2484 assert(IntFlags.local.clear() == IntFlag.undef); 2485 } 2486 2487 private auto binaryImpl(string op, N, M)(const N left, const M right) @safe 2488 if (isFixedPoint!N && isFixedPoint!M) 2489 { 2490 enum wop = (op[$-1] == '=')? op[0 .. $-1] : op; 2491 alias P = OpType!(N, wop, M); 2492 alias R = Select!(wop == op, P, N); 2493 2494 static if (wop.among!("+", "-", "*")) 2495 { 2496 enum isPromSafe = !(isSigned!N || isSigned!M) || (isSigned!P && isSigned!R); 2497 enum needCOp = (wop == "*")? 2498 (precision!N + precision!M) > precision!P : 2499 (max(precision!N, precision!M) + 1) > precision!P; 2500 2501 bool over = false; 2502 static if (needCOp) 2503 { 2504 enum cx = (staticIndexOf!(wop, "+", "-", "*") << 1) + isSigned!P; 2505 alias cop = AliasSeq!(addu, adds, subu, subs, mulu, muls)[cx]; 2506 2507 const pR = cop(cast(P)left, cast(P)right, over); 2508 } 2509 else 2510 const pR = mixin("left " ~ wop ~ " right"); 2511 2512 static if (isSigned!P && trueMin!P < trueMin!R) 2513 { 2514 if (pR < trueMin!R) 2515 over = true; 2516 } 2517 static if (trueMax!P > trueMax!R) 2518 { 2519 if (pR > trueMax!R) 2520 over = true; 2521 } 2522 2523 if (over) 2524 IntFlag.over.raise!policy(); 2525 return cast(R)pR; 2526 } 2527 else static if (wop.among!("/", "%")) 2528 { 2529 enum isPromSafe = !(isSigned!N || isSigned!M) || 2530 (isSigned!P && (wop == "%"? (isSigned!R || !isSigned!N) : isSigned!R)); 2531 2532 const div0 = (right == 0); 2533 static if (isSigned!N && isSigned!M) 2534 const posOver = (left == trueMin!R) && (right == -1); 2535 else 2536 enum posOver = false; 2537 2538 R ret = void; 2539 if (div0 || posOver) 2540 { 2541 (posOver? IntFlag.posOver : IntFlag.div0).raise!policy(); 2542 ret = 0; // Prevent unrecoverable FPE 2543 } 2544 else 2545 ret = cast(R)mixin("left " ~ wop ~ " right"); 2546 2547 return ret; 2548 } 2549 else static if (wop.among!("<<", ">>", ">>>")) 2550 { 2551 enum isPromSafe = !isSigned!N || isSigned!R || (op == ">>>"); 2552 2553 enum invalidSh = ~cast(M)(8 * P.sizeof - 1); 2554 if (right & invalidSh) 2555 IntFlag.undef.raise!policy(); 2556 2557 return cast(R) mixin("cast(P)left " ~ wop ~ " right"); 2558 } 2559 else static if (wop.among!("&", "|", "^")) 2560 { 2561 enum isPromSafe = true; 2562 2563 return cast(R)mixin("left " ~ wop ~ " right"); 2564 } 2565 else 2566 static assert(false); 2567 2568 static assert(isPromSafe, 2569 "The standard " ~ N.stringof ~ " " ~ op ~ " " ~ M.stringof ~ 2570 " operation is unsafe, due to a signed/unsigned mismatch. " ~ 2571 "Use an explicit cast, or switch to smartOp/SmartInt." 2572 ); 2573 } 2574 2575 /** 2576 Perform the binary (two-argument) integer operation specified by `op`. 2577 $(UL 2578 $(LI Unsafe signed/unsigned operations will generate a compile-time error.) 2579 $(LI `+`, `-`, `*`, `/`, and `%` are checked for overflow at runtime.) 2580 $(LI `/` and `%` are also checked for divide-by-zero.) 2581 $(LI `<<`, `>>`, and `>>>` are checked to verify that `right >= 0` and `right < (8 * typeof(left).sizeof)`. 2582 Otherwise, `IntFlag.undef` is raised.) 2583 ) $(BR) 2584 Note also: 2585 $(UL 2586 $(LI The shift operators are $(B not) checked for overflow and should not be used for multiplication, 2587 division, or exponentiation. Instead, use `mulPow2()` and `divPow2()`, which internally use the bitshifts for speed, 2588 but check for overflow and correctly handle negative values.) 2589 $(LI Likewise, `modPow2()` should be used for remainders instead of `&`.) 2590 $(LI `^^` and `^^=` will remain disabled in favour of `pow` until DMD issues 15288 and 15412 are fixed.) 2591 ) $(BR) 2592 Like the standard equivalents, the assignment operators (`+=`, `-=`, `*=`, etc.) take `left` by `ref` and will overwrite 2593 it with the result of the operation. 2594 **/ 2595 OpType!(N, op, M) binary(string op, N, M)(const N left, const M right) @safe 2596 if (isFixedPoint!N && isFixedPoint!M && 2597 op.among!("+", "-", "*", "/", "%", "^^", "<<", ">>", ">>>", "&", "|", "^")) 2598 { 2599 static assert(op != "^^", 2600 "pow() should be used instead of operator ^^ because of issue 15288."); 2601 2602 return binaryImpl!op(left, right); 2603 } 2604 /// ditto 2605 ref N binary(string op, N, M)(return ref N left, const M right) @safe 2606 if (isIntegral!N && isFixedPoint!M && (op[$ - 1] == '=')) 2607 { 2608 static assert(op != "^^=", 2609 "pow() should be used instead of operator ^^= because of issue 15412."); 2610 2611 left = binaryImpl!op(left, right); 2612 return left; 2613 } 2614 /// 2615 unittest 2616 { 2617 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2618 2619 assert(safeOp.binary!"+"(17, -5) == 12); 2620 static assert(!__traits(compiles, safeOp.binary!"+"(-1, 1u))); 2621 2622 ulong a = 18_446_744_073_709_551_615uL; 2623 safeOp.binary!"+="(a, 1u); 2624 assert(IntFlags.local.clear() == IntFlag.posOver); 2625 2626 assert(safeOp.binary!"-"(17u, 5u) == 12u); 2627 safeOp.binary!"-"(5u, 17u); 2628 assert(IntFlags.local.clear() == IntFlag.negOver); 2629 2630 ulong b = 123_456_789_987_654_321uL; 2631 static assert(!__traits(compiles, safeOp.binary!"-="(b, 987_654_321))); 2632 assert(safeOp.binary!"-="(b, 987_654_321u) == 123_456_789_000_000_000uL); 2633 assert(b == 123_456_789_000_000_000uL); 2634 2635 assert(safeOp.binary!"*"(-1 << 30, 2) == int.min); 2636 safeOp.binary!"*"(1 << 30, 2); 2637 assert(IntFlags.local.clear() == IntFlag.negOver); 2638 2639 uint c = 1u << 18; 2640 assert(safeOp.binary!"*="(c, 1u << 4) == 1u << 22); 2641 assert(c == 1u << 22); 2642 2643 assert(safeOp.binary!"/"(22, 11) == 2); 2644 assert(!__traits(compiles, safeOp.binary!"/"(-22, 11u))); 2645 safeOp.binary!"/"(0, 0); 2646 assert(IntFlags.local.clear() == IntFlag.div0); 2647 2648 long j = long.min; 2649 safeOp.binary!"/="(j, -1); 2650 assert(IntFlags.local.clear() == IntFlag.posOver); 2651 2652 assert(safeOp.binary!"%"(20u, 7u) == 6u); 2653 static assert(!__traits(compiles, safeOp.binary!"%"(20u, -7))); 2654 safeOp.binary!"%"(20u, 0u); 2655 assert(IntFlags.local.clear() == IntFlag.div0); 2656 2657 short n = 75; 2658 assert(safeOp.binary!"%="(n, -10) == 5); 2659 assert(n == 5); 2660 } 2661 /// 2662 unittest 2663 { 2664 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2665 2666 assert(safeOp.binary!"<<"(-0x80, 2) == -0x200); 2667 safeOp.binary!"<<"(-0x80, -2); 2668 assert(IntFlags.local.clear() == IntFlag.undef); 2669 2670 ubyte a = 0x3u; 2671 safeOp.binary!"<<="(a, 7); 2672 assert(a == 0x80u); 2673 2674 assert(safeOp.binary!">>"(-0xC, 5u) == -0x1); 2675 safeOp.binary!">>"(-0xC, long.max); 2676 assert(IntFlags.local.clear() == IntFlag.undef); 2677 2678 short b = 0x700; 2679 assert(safeOp.binary!">>="(b, 8) == 0x7); 2680 assert(b == 0x7); 2681 2682 assert(safeOp.binary!">>>"(-0x80, 2u) == 0x3FFF_FFE0); 2683 safeOp.binary!">>>"(-0x80, 32); 2684 assert(IntFlags.local.clear() == IntFlag.undef); 2685 2686 int c = 0xFE_DCBA; 2687 assert(safeOp.binary!">>>="(c, 12) == 0xFED); 2688 assert(c == 0xFED); 2689 2690 assert(safeOp.binary!"&"(0x6Fu, 0x4076) == 0x66u); 2691 2692 ubyte d = 0x6Fu; 2693 assert(safeOp.binary!"&="(d, 0x4076) == 0x66u); 2694 assert(d == 0x66u); 2695 2696 assert(safeOp.binary!"|"(0x6F, 0x4076u) == 0x407Fu); 2697 2698 byte e = 0x6F; 2699 assert(safeOp.binary!"|="(e, 0x4076u) == 0x7F); 2700 assert(e == 0x7F); 2701 2702 assert(safeOp.binary!"^"(0x6F, 0x4076) == 0x4019); 2703 2704 int f = 0x6F; 2705 assert(safeOp.binary!"^="(f, 0x4076) == 0x4019); 2706 assert(f == 0x4019); 2707 2708 assert(!IntFlags.local); 2709 } 2710 2711 /** 2712 Equivalent to `left * pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer alternative to 2713 `left << exp` that is still very fast. 2714 2715 Note that (conceptually) rounding occurs $(I after) the `*`, meaning that `mulPow2(left, -exp)` is equivalent to 2716 `divPow2(left, exp)`. 2717 **/ 2718 auto mulPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2719 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 2720 { 2721 return byPow2Impl!("*", NumFromScal!N, NumFromScal!M)(left, exp); 2722 } 2723 /// ditto 2724 auto mulPow2(N, M)(const N left, const M exp) @safe 2725 if (isFixedPoint!N && isFixedPoint!M) 2726 { 2727 return byPow2Impl!("*", policy, NumFromScal!N, NumFromScal!M)(left, exp); 2728 } 2729 /// 2730 unittest 2731 { 2732 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2733 2734 assert(safeOp.mulPow2(-23, 5) == -736); 2735 safeOp.mulPow2(10_000_000, 10); 2736 assert(IntFlags.local.clear() == IntFlag.posOver); 2737 2738 assert(safeOp.mulPow2(65536, -8) == 256); 2739 assert(safeOp.mulPow2(-100, -100) == 0); 2740 } 2741 2742 /** 2743 Equivalent to `left / pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer alternative to 2744 `left >> exp` that is still very fast. 2745 2746 Note that (conceptually) rounding occurs $(I after) the `/`, meaning that `divPow2(left, -exp)` is equivalent to 2747 `mulPow2(left, exp)`. 2748 **/ 2749 auto divPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2750 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 2751 { 2752 return byPow2Impl!("/", NumFromScal!N, NumFromScal!M)(left, exp); 2753 } 2754 /// ditto 2755 auto divPow2(N, M)(const N left, const M exp) @safe 2756 if (isFixedPoint!N && isFixedPoint!M) 2757 { 2758 return byPow2Impl!("/", policy, NumFromScal!N, NumFromScal!M)(left, exp); 2759 } 2760 /// 2761 unittest 2762 { 2763 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2764 2765 assert(safeOp.divPow2(65536, 8) == 256); 2766 assert(safeOp.divPow2(-100, 100) == 0); 2767 assert(safeOp.divPow2(-23, -5) == -736); 2768 2769 safeOp.divPow2(10_000_000, -10); 2770 assert(IntFlags.local.clear() == IntFlag.posOver); 2771 } 2772 2773 /** 2774 Equivalent to `left % pow(2, exp)`, but faster and works with a wider range of inputs. This is a safer alternative to 2775 `left & ((1 << exp) - 1)` that is still very fast. 2776 **/ 2777 auto modPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2778 if ((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M)) 2779 { 2780 return byPow2Impl!("%", NumFromScal!N, NumFromScal!M)(left, exp); 2781 } 2782 /// ditto 2783 auto modPow2(N, M)(const N left, const M exp) pure @safe nothrow @nogc 2784 if (isFixedPoint!N && isFixedPoint!M) 2785 { 2786 return byPow2Impl!("%", IntFlagPolicy.sticky, NumFromScal!N, NumFromScal!M)(left, exp); 2787 } 2788 /// 2789 unittest 2790 { 2791 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2792 2793 assert(safeOp.modPow2( 101, 1) == 1); 2794 assert(safeOp.modPow2( 101, 3) == 5); 2795 assert(safeOp.modPow2(-101, 3) == -5); 2796 2797 assert(safeOp.modPow2(101, -2) == 0); 2798 assert(safeOp.modPow2(101, 1_000) == 101); 2799 } 2800 2801 /** 2802 Raise `base` to the `exp` power. 2803 2804 Errors that may be signalled if neither input is floating-point: 2805 $(UL 2806 $(LI `IntFlag.posOver` or `IntFlag.negOver` if the absolute value of the result is too large to 2807 represent with the return type.) 2808 $(LI `exp < 0`, `IntFlag.undef` is raised because `std.math.pow` would trigger an FPE given the same input.) 2809 ) 2810 **/ 2811 CallType!(std.math.pow, N, M) pow(N, M)(const N base, const M exp) @safe 2812 if (isFixedPoint!N && isFixedPoint!M) 2813 { 2814 alias R = typeof(return); 2815 static assert(!isSigned!N || isSigned!R, 2816 "std.math.pow(" ~ N.stringof ~ ", " ~ M.stringof ~ 2817 ") is unsafe, due to a signed/unsigned mismatch. Use an explicit cast, or switch to smartOp/SmartInt." 2818 ); 2819 2820 auto po = powImpl!(R, Select!(isSigned!M, long, ulong))(base, exp); 2821 static assert(is(typeof(po.num) == R)); 2822 if (exp < 0) 2823 po.flag = IntFlag.undef; 2824 2825 if (!po.flag.isNull) 2826 po.flag.raise!policy(); 2827 return po.num; 2828 } 2829 /// 2830 unittest 2831 { 2832 import checkedint.sticky : safeOp; // use IntFlagPolicy.sticky 2833 2834 assert(safeOp.pow(-10, 3) == -1_000); 2835 static assert(!__traits(compiles, safeOp.pow(16, 4uL))); 2836 safeOp.pow(2, -1); 2837 assert(IntFlags.local.clear() == IntFlag.undef); 2838 2839 safeOp.pow(-3, 27); 2840 assert(IntFlags.local.clear() == IntFlag.negOver); 2841 safeOp.pow(0, -5); 2842 assert(IntFlags.local.clear() == IntFlag.undef); 2843 } 2844 } 2845 private alias safeOp(bool throws) = safeOp!(cast(Flag!"throws")throws); 2846 2847 // conv ///////////////////////////////////////////////// 2848 /** 2849 A wrapper for `std.conv.to()` which uses `checkedint.flags` for error signaling when converting between any combination 2850 of basic scalar types and `checkedint` types. With an appropriate `policy`, this allows 2851 $(LINK2 ./package.html#to, `checkedint.to()`) to be used for numeric conversions in `pure nothrow` code, unlike 2852 `std.conv.to()`. 2853 2854 Conversions involving any other type are simply forwarded to `std.conv.to()`, with no runtime overhead. 2855 **/ 2856 template to(T, IntFlagPolicy policy) 2857 { 2858 private enum useFlags(S) = (isCheckedInt!T || isScalarType!T) && (isCheckedInt!S || isScalarType!S); 2859 private enum reqAttrs = 2860 ((policy == IntFlagPolicy.sticky || policy == IntFlagPolicy.asserts)? " nothrow" : "") ~ 2861 ((policy == IntFlagPolicy.asserts || policy == IntFlagPolicy.throws)? " pure" : ""); 2862 2863 T to(S)(const S value) @safe 2864 if (useFlags!S) 2865 { 2866 static if (isCheckedInt!T || isCheckedInt!S) 2867 return T(checkedint.to!(BasicScalar!T, policy)(value.bscal)); 2868 else 2869 { 2870 static if (policy != IntFlagPolicy.none && !isFloatingPoint!T) 2871 { 2872 static if (isFloatingPoint!S) 2873 { 2874 if (value >= trueMin!T) 2875 { 2876 if (value > trueMax!T) 2877 IntFlag.posOver.raise!policy(); 2878 } 2879 else 2880 (std.math.isNaN(value)? IntFlag.undef : IntFlag.negOver).raise!policy(); 2881 } 2882 else 2883 { 2884 static if (cast(long)trueMin!S < cast(long)trueMin!T) 2885 { 2886 if (value < cast(S)trueMin!T) 2887 IntFlag.negOver.raise!policy(); 2888 } 2889 static if (cast(ulong)trueMax!S > cast(ulong)trueMax!T) 2890 { 2891 if (value > cast(S)trueMax!T) 2892 IntFlag.posOver.raise!policy(); 2893 } 2894 } 2895 } 2896 return cast(T)value; 2897 } 2898 } 2899 2900 mixin(` 2901 T to(S)(S value)` ~ reqAttrs ~ ` 2902 if (!useFlags!S) 2903 { 2904 import std.conv : impl = to; 2905 return impl!T(value); 2906 }`); 2907 } 2908 /// 2909 unittest 2910 { 2911 // Conversions involving only basic scalars or checkedint types use IntFlags for error signalling. 2912 import checkedint.sticky : smartInt, SmartInt, smartOp, to; // use IntFlagPolicy.sticky 2913 2914 assert(to!int(smartInt(-421751L)) == -421751); 2915 assert(to!(SmartInt!ubyte)(100) == 100u); 2916 2917 assert(is(typeof(to!int(50u)) == int)); 2918 assert(to!int(50u) == 50); 2919 assert(!IntFlags.local); 2920 2921 // If IntFlagPolicy.sticky is set, failed conversions return garbage, but... 2922 assert(smartOp.cmp!"!="(to!int(uint.max), uint.max)); 2923 // ...IntFlags.local can be checked to see if anything went wrong. 2924 assert(IntFlags.local.clear() == IntFlag.posOver); 2925 } 2926 /// 2927 unittest 2928 { 2929 // Everything else forwards to std.conv.to(). 2930 assert(to!(string, IntFlagPolicy.throws)(55) == "55"); 2931 assert(to!(real, IntFlagPolicy.throws)("3.141519e0") == 3.141519L); 2932 2933 // Setting IntFlagPolicy.sticky or .asserts will block std.conv.to(), unless the instantiation is nothrow. 2934 // Setting IntFlagPolicy.asserts or .throws will block std.conv.to(), unless the instantiation is pure. 2935 static assert(!__traits(compiles, to!(real, IntFlagPolicy.sticky)("3.141519e0"))); 2936 } 2937 2938 @property { 2939 /** 2940 Get a view or copy of `num` as a basic scalar. 2941 2942 Useful in generic code that handles both basic types, and `checkedint` types. 2943 **/ 2944 ref inout(N) bscal(N)(return ref inout(N) num) @safe 2945 if (isScalarType!N) 2946 { 2947 return num; 2948 } 2949 /// ditto 2950 ref inout(N) bscal(N)(return ref inout(N) num) @safe 2951 if (isCheckedInt!N) 2952 { 2953 return num.bscal; 2954 } 2955 /// ditto 2956 N bscal(N)(const N num) @safe 2957 if (isScalarType!N) 2958 { 2959 return num; 2960 } 2961 /// ditto 2962 BasicScalar!N bscal(N)(const N num) @safe 2963 if (isCheckedInt!N) 2964 { 2965 return num.bscal; 2966 } 2967 /// 2968 unittest 2969 { 2970 import checkedint.throws : smartInt, SmartInt; // use IntFlagPolicy.throws 2971 2972 assert(is(typeof(bscal(2u)) == uint)); 2973 assert(is(typeof(bscal(SmartInt!int(2))) == int)); 2974 2975 assert(bscal(-3153) == -3153); 2976 assert(bscal(smartInt(75_000)) == 75_000); 2977 } 2978 2979 /** 2980 Get a view or copy of `num` that supports bitwise operations. 2981 2982 Useful in generic code that handles both basic types and `checkedint` types. 2983 **/ 2984 ref inout(N) bits(N)(return ref inout(N) num) @safe 2985 if (isFixedPoint!N) 2986 { 2987 return num; 2988 } 2989 /// ditto 2990 ref inout(N) bits(N)(return ref inout(N) num) @safe 2991 if (isCheckedInt!N) 2992 { 2993 return num.bits; 2994 } 2995 /// ditto 2996 N bits(N)(const N num) @safe 2997 if (isFixedPoint!N) 2998 { 2999 return num; 3000 } 3001 /// ditto 3002 SmartInt!(BasicScalar!N, N.policy, Yes.bitOps) bits(N)(const N num) @safe 3003 if (isSmartInt!N) 3004 { 3005 return num.bits; 3006 } 3007 /// ditto 3008 SafeInt!(BasicScalar!N, N.policy, Yes.bitOps) bits(N)(const N num) @safe 3009 if (isSafeInt!N) 3010 { 3011 return num.bits; 3012 } 3013 /// 3014 unittest 3015 { 3016 import checkedint.throws : SmartInt; // use IntFlagPolicy.throws 3017 3018 assert(is(typeof(bits(5)) == int)); 3019 3020 SmartInt!(int, No.bitOps) noBits = 5; 3021 assert(is(typeof(bits(noBits)) == SmartInt!(int, Yes.bitOps))); 3022 3023 static assert(!__traits(compiles, noBits << 2)); 3024 assert((bits(noBits) << 2) == 20); 3025 } 3026 3027 /** 3028 Cast `num` to a basic type suitable for indexing an array. 3029 3030 For signed types, `ptrdiff_t` is returned. For unsigned types, `size_t` is returned. 3031 **/ 3032 Select!(isSigned!N, ptrdiff_t, size_t) idx(IntFlagPolicy policy, N)(const N num) @safe 3033 if (isScalarType!N || isCheckedInt!N) 3034 { 3035 return to!(typeof(return), policy)(num.bscal); 3036 } 3037 /// ditto 3038 Select!(isSigned!(BasicScalar!N), ptrdiff_t, size_t) idx(N)(const N num) @safe 3039 if (isCheckedInt!N) 3040 { 3041 return num.idx; 3042 } 3043 /// 3044 unittest 3045 { 3046 import checkedint.sticky : idx, SmartInt, safeInt; // use IntFlagPolicy.sticky 3047 3048 assert(is(typeof(idx(cast(long)1)) == ptrdiff_t)); 3049 assert(is(typeof(idx(cast(ubyte)1)) == size_t)); 3050 assert(is(typeof(idx(SmartInt!ulong(1))) == size_t)); 3051 3052 assert(idx(17uL) == 17); 3053 assert(idx(-3) == -3); 3054 assert(idx(safeInt(cast(byte)100)) == 100); 3055 3056 static if (size_t.sizeof == 4) 3057 { 3058 idx(ulong.max); 3059 assert(IntFlags.local.clear() == IntFlag.posOver); 3060 3061 idx(long.min); 3062 assert(IntFlags.local.clear() == IntFlag.negOver); 3063 } 3064 } 3065 } 3066 /+}+/ 3067 3068 // traits ///////////////////////////////////////////////// 3069 3070 /// Evaluates to `true` if `T` is an instance of $(LINK2 ./package.html#SafeInt, `SafeInt`). 3071 enum isSafeInt(T) = isInstanceOf!(SafeInt, T); 3072 /// 3073 unittest 3074 { 3075 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3076 3077 assert( isSafeInt!(SafeInt!int)); 3078 3079 assert(!isSafeInt!int); 3080 assert(!isSafeInt!(SmartInt!int)); 3081 } 3082 3083 /// Evaluates to `true` if `T` is an instance of $(LINK2 ./package.html#SmartInt, `SmartInt`). 3084 enum isSmartInt(T) = isInstanceOf!(SmartInt, T); 3085 /// 3086 unittest 3087 { 3088 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3089 3090 assert( isSmartInt!(SmartInt!int)); 3091 3092 assert(!isSmartInt!int); 3093 assert(!isSmartInt!(SafeInt!int)); 3094 } 3095 3096 /** 3097 Evaluates to `true` if `T` is an instance of $(LINK2 ./package.html#SafeInt, `SafeInt`) 3098 or $(LINK2 ./package.html#SmartInt, `SmartInt`). 3099 **/ 3100 enum isCheckedInt(T) = isSafeInt!T || isSmartInt!T; 3101 /// 3102 unittest 3103 { 3104 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3105 3106 assert( isCheckedInt!(SafeInt!int)); 3107 assert( isCheckedInt!(SmartInt!int)); 3108 3109 assert(!isCheckedInt!int); 3110 } 3111 3112 /** 3113 Evaluates to `true` if either: 3114 $(UL 3115 $(LI `isScalarType!T`, or) 3116 $(LI `isCheckedInt!T`) 3117 ) 3118 $(B And) bitwise operators such as `<<` and `~` are available for `T`. 3119 **/ 3120 template hasBitOps(T) 3121 { 3122 static if (isCheckedInt!T) 3123 enum hasBitOps = TemplateArgsOf!T[2]; 3124 else 3125 enum hasBitOps = isFixedPoint!T; 3126 } 3127 /// 3128 unittest 3129 { 3130 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3131 3132 assert( hasBitOps!(SafeInt!(int, Yes.bitOps))); 3133 assert( hasBitOps!(SmartInt!(int, Yes.bitOps))); 3134 assert( hasBitOps!int); 3135 assert( hasBitOps!bool); 3136 assert( hasBitOps!dchar); 3137 3138 assert(!hasBitOps!(SafeInt!(int, No.bitOps))); 3139 assert(!hasBitOps!(SmartInt!(int, No.bitOps))); 3140 assert(!hasBitOps!float); 3141 } 3142 3143 /** 3144 Aliases to the basic scalar type associated with `T`, assuming either: 3145 $(UL 3146 $(LI `isScalarType!T`, or) 3147 $(LI `isCheckedInt!T`) 3148 ) 3149 Otherwise, `BasicScalar` aliases to `void`. 3150 **/ 3151 template BasicScalar(T) 3152 { 3153 static if (isScalarType!T) 3154 alias BasicScalar = Unqual!T; 3155 else static if (isCheckedInt!T) 3156 alias BasicScalar = TemplateArgsOf!T[0]; 3157 else 3158 alias BasicScalar = void; 3159 } 3160 /// 3161 unittest 3162 { 3163 import checkedint.throws : SmartInt, SafeInt; // use IntFlagPolicy.throws 3164 3165 assert(is(BasicScalar!(SafeInt!int) == int)); 3166 assert(is(BasicScalar!(SmartInt!ushort) == ushort)); 3167 3168 assert(is(BasicScalar!int == int)); 3169 assert(is(BasicScalar!(const shared real) == real)); 3170 } 3171 3172 // internal ///////////////////////////////////////////////// 3173 private 3174 { 3175 enum N trueMin(N) = mostNegative!N; 3176 template trueMax(N) 3177 if (isScalarType!N) 3178 { 3179 static if (is(Unqual!N == dchar)) 3180 enum N trueMax = ~cast(N)0; 3181 else 3182 enum N trueMax = N.max; 3183 } 3184 3185 template NumFromScal(N) 3186 if (isScalarType!N) 3187 { 3188 static if (isNumeric!N) 3189 alias NumFromScal = N; 3190 else static if (isSomeChar!N) 3191 alias NumFromScal = IntFromChar!N; 3192 else //if (isBoolean!N) 3193 alias NumFromScal = ubyte; 3194 } 3195 3196 pragma(inline, true) 3197 { 3198 int bsfImpl(IntFlagPolicy policy, N)(const N num) @safe 3199 if (isFixedPoint!N) 3200 { 3201 static if (isSigned!N) 3202 return bsfImpl!(policy, Unsigned!N)(num); 3203 else 3204 { 3205 static assert(N.sizeof <= ulong.sizeof); 3206 3207 int ret = void; 3208 if (num == 0) 3209 { 3210 IntFlag.undef.raise!policy(); 3211 ret = int.min; 3212 } 3213 else 3214 ret = bsf(num); 3215 3216 return ret; 3217 } 3218 } 3219 int bsrImpl(IntFlagPolicy policy, N)(const N num) @safe 3220 if (isFixedPoint!N) 3221 { 3222 static if (isSigned!N) 3223 return bsrImpl!(policy, Unsigned!N)(num); 3224 else 3225 { 3226 static assert(N.sizeof <= ulong.sizeof); 3227 3228 int ret = void; 3229 if (num == 0) 3230 { 3231 IntFlag.undef.raise!policy(); 3232 ret = int.min; 3233 } 3234 else 3235 ret = bsr(num); 3236 3237 return ret; 3238 } 3239 } 3240 3241 auto byPow2Impl(string op, N, M)(const N left, const M exp) pure @safe nothrow @nogc 3242 if (op.among!("*", "/", "%") && ((isFloatingPoint!N && isNumeric!M) || (isNumeric!N && isFloatingPoint!M))) 3243 { 3244 import std.math : exp2, isFinite, frexp, ldexp; 3245 3246 enum wantPrec = max(precision!N, precision!M); 3247 alias R = 3248 Select!(wantPrec <= precision!float, float, 3249 Select!(wantPrec <= precision!double, double, real)); 3250 3251 static if (isFloatingPoint!M) 3252 { 3253 R ret = void; 3254 3255 static if (op.among!("*", "/")) 3256 { 3257 if (left == 0 && exp.isFinite) 3258 ret = 0; 3259 else 3260 { 3261 R wexp = cast(R)exp; 3262 static if (op == "/") 3263 wexp = -wexp; 3264 3265 ret = cast(R)left * exp2(wexp); 3266 } 3267 } 3268 else 3269 { 3270 const p2 = exp2(cast(R)exp); 3271 ret = 3272 p2.isFinite? cast(R)left % p2 : 3273 (p2 > 0)? cast(R)left : 3274 (p2 < 0)? cast(R)0 : 3275 R.nan; 3276 } 3277 3278 return ret; 3279 } 3280 else 3281 { 3282 static if (op.among!("*", "/")) 3283 { 3284 int wexp = 3285 (exp > int.max)? int.max : 3286 (cast(long)exp < -int.max)? -int.max : cast(int)exp; 3287 static if (op == "/") 3288 wexp = -wexp; 3289 3290 return ldexp(cast(R)left, wexp); 3291 } 3292 else 3293 { 3294 int expL; 3295 real mantL = frexp(left, expL); 3296 3297 static if (!isSigned!M) 3298 const retL = expL <= exp; 3299 else 3300 const retL = (expL < 0) || (expL <= exp); 3301 3302 R ret = void; 3303 if (retL) 3304 ret = left; 3305 else 3306 { 3307 const expDiff = expL - exp; 3308 ret = (expDiff > N.mant_dig)? 3309 cast(R)0 : 3310 left - ldexp(floor(ldexp(mantissa, expDiff)), expL - expDiff); 3311 } 3312 3313 return ret; 3314 } 3315 } 3316 } 3317 auto byPow2Impl(string op, IntFlagPolicy policy, N, M)(const N left, const M exp) @safe 3318 if (op.among!("*", "/", "%") && isIntegral!N && isIntegral!M) 3319 { 3320 alias R = Select!(op.among!("*", "/") != 0, Promoted!N, N); 3321 enum Unsigned!M maxSh = 8 * N.sizeof - 1; 3322 3323 R ret = void; 3324 static if (op.among!("*", "/")) 3325 { 3326 const rc = cast(R)left; 3327 const negE = exp < 0; 3328 const absE = cast(Unsigned!M)(negE? 3329 -exp : 3330 exp); 3331 const bigSh = (absE > maxSh); 3332 3333 R back = void; 3334 if ((op == "*")? negE : !negE) 3335 { 3336 if (bigSh) 3337 ret = 0; 3338 else 3339 { 3340 // ">>" rounds as floor(), but we want trunc() like "/" 3341 ret = (rc < 0)? 3342 -(-rc >>> absE) : 3343 rc >>> absE; 3344 } 3345 } 3346 else 3347 { 3348 if (bigSh) 3349 { 3350 ret = 0; 3351 back = 0; 3352 } 3353 else 3354 { 3355 ret = rc << absE; 3356 back = ret >> absE; 3357 } 3358 3359 if (back != rc) 3360 IntFlag.over.raise!policy(); 3361 } 3362 } 3363 else 3364 { 3365 if (exp & ~maxSh) 3366 ret = (exp < 0)? 0 : left; 3367 else 3368 { 3369 const mask = ~(~cast(N)0 << exp); 3370 ret = cast(R)(left < 0? 3371 -(-left & mask) : 3372 left & mask); 3373 } 3374 } 3375 3376 return ret; 3377 } 3378 } 3379 3380 struct PowOut(B) 3381 { 3382 B num; 3383 IntFlag flag; 3384 } 3385 3386 // Minimize template bloat by using a common pow() implementation 3387 PowOut!B powImpl(B, E)(const B base, const E exp) @safe 3388 if ((is(B == int) || is(B == uint) || is(B == long) || is(B == ulong)) && 3389 (is(E == long) || is(E == ulong))) 3390 { 3391 pragma(inline, false); 3392 PowOut!B ret; 3393 3394 static if (isSigned!B) 3395 { 3396 alias cmul = muls; 3397 const smallB = (1 >= base && base >= -1); 3398 } 3399 else 3400 { 3401 alias cmul = mulu; 3402 const smallB = (base <= 1); 3403 } 3404 3405 if (smallB) 3406 { 3407 if (base == 0) 3408 { 3409 static if (isSigned!E) 3410 { 3411 if (exp < 0) 3412 ret.flag = IntFlag.div0; 3413 } 3414 3415 ret.num = (exp == 0); 3416 } 3417 else 3418 ret.num = (exp & 0x1)? base : 1; 3419 3420 return ret; 3421 } 3422 if (exp <= 0) 3423 { 3424 ret.num = (exp == 0); 3425 return ret; 3426 } 3427 3428 ret.num = 1; 3429 if (exp <= precision!B) 3430 { 3431 B b = base; 3432 int e = cast(int)exp; 3433 if (e & 0x1) 3434 ret.num = b; 3435 e >>>= 1; 3436 3437 bool over = false; 3438 while (e != 0) 3439 { 3440 b = cmul(b, b, over); 3441 if (e & 0x1) 3442 ret.num = cmul(ret.num, b, over); 3443 3444 e >>>= 1; 3445 } 3446 3447 if (!over) 3448 return ret; 3449 } 3450 3451 ret.flag = (base < 0 && (exp & 0x1))? 3452 IntFlag.negOver : 3453 IntFlag.posOver; 3454 return ret; 3455 } 3456 }