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