1 /**
2 Copyright: Copyright Thomas Stuart Bockman 2015
3 License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
4 Authors: Thomas Stuart Bockman
5 */
6 
7 module checkedint.smartop;
8 import checkedint.flags, checkedint.internal;
9 
10 import core.checkedint, std.algorithm, std.array, future.traits;
11 static import std.math;
12 
13 // Watch out for DMD issue 15483 - Avoiding multiple return statements may help.
14 @safe: /+pragma(inline, true):+/
15 
16 // TODO: Should this module accept SafeInt or SmartInt operands? It could theoretically do so without importing checkedint:
17 /+private @property pure nothrow @nogc {
18     ref N bscal(N)(return ref N num)
19         if(isScalarType!N)
20     {
21         return num;
22     }
23     N bscal(N)(const N num)
24         if(isScalarType!N)
25     {
26         return num;
27     }
28 }
29 private alias BasicScalar(T) = typeof(function() {
30     static if(__traits(compiles, T.init.bscal))
31         return T.init.bscal;
32     else
33         return;
34 }());
35 +/
36 
37 private void cmpTypeCheck(N, M)() {
38     static assert(isBoolean!N == isBoolean!M,
39         "The intent of a direct comparison of " ~
40         N.stringof ~ " with " ~ M.stringof ~
41         " is unclear. Add an explicit cast."
42     );
43 }
44 
45 bool cmp(string op, N, M)(const N left, const M right) pure nothrow @nogc
46     if(isScalarType!N && isScalarType!M)
47 {
48     cmpTypeCheck!(N, M)();
49 
50     static if(isSigned!N != isSigned!M) {
51         static if(isSigned!N) {
52             if(left < 0)
53                 return mixin("-1 " ~ op ~ " 0");
54         } else {
55             if(right < 0)
56                 return mixin("0 " ~ op ~ " -1");
57         }
58     }
59 
60     return mixin("left " ~ op ~ " right");
61 }
62 int cmp(N, M)(const N left, const M right) pure nothrow @nogc
63     if(isScalarType!N && isScalarType!M)
64 {
65     cmpTypeCheck!(N, M)();
66 
67     static if(isFloatingPoint!N || isFloatingPoint!M)
68         return std.math.cmp(left, right);
69     else {
70         static if(isSigned!N != isSigned!M) {
71             static if(isSigned!N) {
72                 if(left < 0)
73                     return -1;
74             } else {
75                 if(right < 0)
76                     return  1;
77             }
78         }
79 
80         return (left < right)? -1 : (right < left);
81     }
82 }
83 
84 N unary(string op, bool throws, N)(const N num)
85     if(isIntegral!N && op == "~")
86 {
87     return ~num;
88 }
89 N unary(string op, N)(const N num) pure
90     if(isIntegral!N && op == "~")
91 {
92     return unary!(op, true)(num);
93 }
94 IntFromChar!N unary(string op, bool throws, N)(const N num)
95     if(isSomeChar!N && op == "~")
96 {
97     return ~num;
98 }
99 IntFromChar!N unary(string op, N)(const N num) pure
100     if(isSomeChar!N && op == "~")
101 {
102     return unary!(op, true)(num);
103 }
104 Signed!(Promoted!N) unary(string op, bool throws, N)(const N num)
105     if(isFixedPoint!N && op.among!("-", "+"))
106 {
107     alias R = typeof(return);
108     alias UR = Unsigned!R;
109 
110     static if(op == "-") {
111         static if(isSigned!N) {
112             if(num < -R.max)
113                 IntFlag.posOver.raise!throws();
114         } else {
115             if(num > cast(UR)R.min)
116                 IntFlag.negOver.raise!throws();
117         }
118 
119         return -cast(R)num;
120     } else {
121         static if(!isSigned!N) {
122             if(num > R.max)
123                 IntFlag.posOver.raise!throws();
124         }
125 
126         return num;
127     }
128 }
129 Signed!(Promoted!N) unary(string op, N)(const N num) pure
130     if(isIntegral!N && op.among!("-", "+"))
131 {
132     return unary!(op, true)(num);
133 }
134 /+ref+/ N unary(string op, bool throws, N)(/+return+/ ref N num)
135     if(isIntegral!N && op.among!("++", "--"))
136 {
137     static if(op == "++") {
138         if(num >= N.max)
139             IntFlag.posOver.raise!throws();
140 
141         return ++num;
142     } else static if(op == "--") {
143         if(num <= N.min)
144             IntFlag.negOver.raise!throws();
145 
146         return --num;
147     } else
148         static assert(false);
149 }
150 /+ref+/ N unary(string op, N)(/+return+/ ref N num) pure
151     if(isIntegral!N && op.among!("++", "--"))
152 {
153     return unary!(op, true)(num);
154 }
155 
156 Unsigned!N abs(N)(const N num) pure nothrow @nogc
157     if(isIntegral!N)
158 {
159     static if(!isSigned!N)
160         return num;
161     else
162         return cast(typeof(return))(num < 0?
163             -num : // -num doesn't need to be checked for overflow
164              num);
165 }
166 IntFromChar!N abs(N)(const N num) pure nothrow @nogc
167     if(isSomeChar!N)
168 {
169     return num;
170 }
171 
172 ubyte bsf(bool throws, N)(const N num)
173     if(isFixedPoint!N)
174 {
175     return cast(ubyte) bsfImpl!throws(num);
176 }
177 ubyte bsf(N)(const N num) pure
178     if(isFixedPoint!N)
179 {
180     return bsf!true(num);
181 }
182 ubyte bsr(bool throws, N)(const N num)
183     if(isFixedPoint!N)
184 {
185     return cast(ubyte) bsrImpl!throws(num);
186 }
187 ubyte bsr(N)(const N num) pure
188     if(isFixedPoint!N)
189 {
190     return bsr!true(num);
191 }
192 
193 ubyte ilogb(bool throws, N)(const N num)
194     if(isFixedPoint!N)
195 {
196     return cast(ubyte) bsrImpl!throws(abs(num));
197 }
198 ubyte ilogb(N)(const N num) pure
199     if(isFixedPoint!N)
200 {
201     return ilogb!true(num);
202 }
203 
204 /* TODO: Optimize opOpAssign()
205    TODO: Figure out how to get % and / to inline on DMD
206    TODO: Audit binary() to ensure it properly accounts for the fact that (dchar.max < ~cast(dchar)0). */
207 private template Result(N, string op, M)
208     if(isScalarType!N && isScalarType!M)
209 {
210 private:
211     alias WN = NumFromScal!N;
212     alias WM = NumFromScal!M;
213 
214     enum reqFloat = isFloatingPoint!WN || isFloatingPoint!WM;
215     enum precN = precision!WN, precM = precision!WM;
216     enum precStd = reqFloat? precision!float : precision!uint;
217     enum smallSub = (op == "-") && precN < precision!int && precM < precision!int;
218 
219     enum reqSign = reqFloat ||
220         (op.among!("+", "-", "*" , "/") && (isSigned!WN || isSigned!WM || smallSub)) ||
221         (op.among!("%", "^^", "<<", ">>", ">>>") && isSigned!WN) ||
222         (op.among!("&", "|", "^") && (isSigned!WN && isSigned!WM));
223 
224     enum reqPrec = reqFloat? max(precStd, precN, precM) :
225         op.among!("+", "-", "*")? max(precStd, precN, precM) - 1 :
226         op == "/"? (isSigned!WM? max(precStd, precN) - 1 : precN) :
227         op == "%"? min(precision!WN, precision!WM) :
228         op == "^^"? max(precStd - 1, precN) :
229         op.among!("<<", ">>", ">>>")? precN :
230       /+op.among!("&", "|", "^")?+/ max(precN, precM);
231 
232 public:
233     alias Result = Select!(reqFloat,
234         Select!(reqPrec <= double.mant_dig || double.mant_dig >= real.mant_dig,
235             Select!(reqPrec <= float.mant_dig, float, double),
236             real),
237         Select!(reqSign,
238             Select!(reqPrec <= 15,
239                 Select!(reqPrec <= 7, byte, short),
240                 Select!(reqPrec <= 31, int, long)),
241             Select!(reqPrec <= 16,
242                 Select!(reqPrec <= 8, ubyte, ushort),
243                 Select!(reqPrec <= 32, uint, ulong))));
244 }
245 auto binary(string op, bool throws, N, M)(const N left, const M right)
246     if(isIntegral!N && isIntegral!M)
247 {
248     alias UN = Unsigned!N;
249     alias UM = Unsigned!M;
250     alias  R = Result!(N, op, M);
251     alias UR = Unsigned!R;
252 
253     static if(op.among!("+", "-")) {
254         alias UP = Select!(precision!N <= 32 && precision!M <= 32, uint, ulong);
255         alias W = Select!(isSigned!N && isSigned!M, Signed!UP, UP);
256 
257         static if(!isSigned!W &&  isSigned!R) {
258             alias cop = Select!(op == "+", addu, subu);
259 
260             bool hiBit = false;
261             const wRet = cop(cast(W)left, cast(W)right, hiBit);
262             const bool wSign = (Select!(isSigned!N, left, right) < 0) ^ hiBit;
263 
264             static assert(is(R == Signed!W));
265             const ret = cast(R)wRet;
266             const over = (ret < 0) != wSign;
267         } else {
268             static assert(is(R == W));
269 
270             static if(precision!W > max(precision!N, precision!M)) {
271                 enum over = false;
272                 const ret = mixin("cast(W)left " ~ op ~ " cast(W)right");
273             } else {
274                 alias cop = Select!(isSigned!W,
275                     Select!(op == "+", adds, subs),
276                     Select!(op == "+", addu, subu));
277 
278                 bool over = false;
279                 const ret = cop(cast(W)left, cast(W)right, over);
280             }
281         }
282 
283         if(over)
284             IntFlag.over.raise!throws();
285         return ret;
286     } else
287     static if(op == "*") {
288         static if(!isSigned!N &&  isSigned!M)
289             // Integer multiplication is commutative
290             return binary!("*", throws)(right, left);
291         else {
292             alias cop = Select!(isSigned!R, muls, mulu);
293 
294             bool over = false;
295             static if( isSigned!N && !isSigned!M) {
296                 if(right > cast(UR)R.max) {
297                     if(left == 0)
298                         return cast(R)0;
299                     if(left == -1 && right == cast(UR)R.min)
300                         return R.min;
301 
302                     over = true;
303                 }
304             }
305             const ret = cop(cast(R)left, cast(R)right, over);
306 
307             if(over)
308                 IntFlag.over.raise!throws();
309             return ret;
310         }
311     } else
312     static if(op == "/") {
313         if(right == 0) {
314             IntFlag.div0.raise!throws();
315             return cast(R)0; // Prevent unrecoverable FPE
316         }
317 
318         alias UP = Select!(precision!N <= 32 && precision!M <= 32, uint, ulong);
319         alias  W = Select!(isSigned!N && isSigned!M, Signed!UP, UP);
320 
321         static if(!isSigned!W &&  isSigned!R) {
322             W wL = void;
323             W wG = void;
324             static if(isSigned!N) {
325                 const negR = left < 0;
326                 alias side = left;
327                 alias wS = wL;
328                 wG = cast(W)right;
329             } else {
330                 const negR = right < 0;
331                 wL = cast(W)left;
332                 alias side = right;
333                 alias wS = wG;
334             }
335 
336             bool over = void;
337             R ret = void;
338             if(negR) {
339                 wS = -cast(W)side;
340                 const W wR = wL / wG;
341 
342                 over = (wR > cast(UR)R.min);
343                 ret = -cast(R)wR;
344             } else {
345                 wS =  cast(W)side;
346                 const W wR = wL / wG;
347 
348                 over = (wR > cast(UR)R.max);
349                 ret =  cast(R)wR;
350             }
351 
352             if(over)
353                 IntFlag.over.raise!throws();
354             return ret;
355         } else {
356             static if(isSigned!N && isSigned!M) {
357                 if(left == R.min && right == -1) {
358                     IntFlag.posOver.raise!throws();
359                     return cast(R)0; // Prevent unrecoverable FPE
360                 }
361             }
362 
363             return cast(R)(left / right);
364         }
365     } else
366     static if(op == "%") {
367         UM wG = void;
368         if(right >= 0) {
369             if(right == 0) {
370                 IntFlag.undef.raise!throws();
371                 return cast(R)0; // Prevent unrecoverable FPE
372             }
373 
374             wG = cast(UM) right;
375         } else
376             wG = cast(UM)-right;
377 
378         if(wG > N.max) {
379             return (wG == cast(UN)N.min && left == N.min)?
380                 cast(R)0 :
381                 cast(R)left;
382         }
383 
384         return cast(R)(left % cast(N)wG);
385     } else
386     static if(op.among!("<<", ">>", ">>>")) {
387         const negG = right < 0;
388         const shR = (op == "<<")?
389              negG :
390             !negG;
391 
392         R ret = void;
393         static if(op == ">>>")
394             const wL = cast(UN)left;
395         else
396             alias wL = left;
397         const absG = negG?
398             -cast(UM)right :
399              cast(UM)right;
400 
401         enum maxSh = (8 * N.sizeof) - 1;
402         if(absG <= maxSh) {
403             const wG = cast(int)absG;
404             ret = cast(R)(shR?
405                 wL >> wG :
406                 wL << wG);
407         } else {
408             ret = cast(R)((isSigned!N && (op != ">>>") && shR)?
409                 (wL >> maxSh) :
410                 0);
411         }
412 
413         return ret;
414     } else
415     static if(op.among!("&", "|", "^"))
416         return cast(R)mixin("left " ~ op ~ " right");
417     else {
418         static assert(op != "^^",
419             "pow() should be used instead of operator ^^ because of issue 15288.");
420 
421         static assert(false, op ~ " is not implemented for integers.");
422     }
423 }
424 auto binary(string op, N, M)(const N left, const M right) pure
425     if(isIntegral!N && isIntegral!M)
426 {
427     return binary!(op, true)(left, right);
428 }
429 
430 auto mulPow2(N, M)(const N coef, const M exp) pure nothrow @nogc
431     if((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M))
432 {
433     return byPow2Impl!("*", NumFromScal!N, NumFromScal!M)(coef, exp);
434 }
435 auto mulPow2(bool throws, N, M)(const N coef, const M exp)
436     if(isFixedPoint!N && isFixedPoint!M)
437 {
438     return byPow2Impl!("*", throws, NumFromScal!N, NumFromScal!M)(coef, exp);
439 }
440 auto mulPow2(N, M)(const N coef, const M exp) pure
441     if(isFixedPoint!N && isFixedPoint!M)
442 {
443     return mulPow2!true(coef, exp);
444 }
445 auto divPow2(N, M)(const N coef, const M exp) pure nothrow @nogc
446     if((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M))
447 {
448     return byPow2Impl!("/", NumFromScal!N, NumFromScal!M)(coef, exp);
449 }
450 auto divPow2(bool throws, N, M)(const N coef, const M exp)
451     if(isFixedPoint!N && isFixedPoint!M)
452 {
453     return byPow2Impl!("/", throws, NumFromScal!N, NumFromScal!M)(coef, exp);
454 }
455 auto divPow2(N, M)(const N coef, const M exp) pure
456     if(isFixedPoint!N && isFixedPoint!M)
457 {
458     return divPow2!true(coef, exp);
459 }
460 
461 auto pow(N, M)(const N base, const M exp) pure nothrow @nogc
462     if((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M))
463 {
464     alias R = Result!(N, "^^", M);
465     static assert(is(typeof(return) == R));
466     return std.math.pow(cast(R)base, exp);
467 }
468 auto pow(bool throws, N, M)(const N base, const M exp)
469     if(isFixedPoint!N && isFixedPoint!M)
470 {
471     alias R = Result!(N, "^^", M);
472 
473     IntFlag flag;
474     const ret = powImpl!(R, Select!(isSigned!M, long, ulong))(base, exp, flag);
475     static assert(is(typeof(ret) == const(R)));
476 
477     if(!flag.isNull)
478         flag.raise!throws();
479     return ret;
480 }
481 auto pow(N, M)(const N base, const M exp) pure
482     if(isFixedPoint!N && isFixedPoint!M)
483 {
484     return pow!true(base, exp);
485 }