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