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.safeop;
8 import checkedint.flags, checkedint.internal;
9 
10 import core.checkedint, std.algorithm, std.array, future.traits;
11 static import std.math;
12 static if(__VERSION__ >= 2068) {
13     version(GNU) { static assert(false); }
14     import std.meta;
15 } else {
16     import std.typetuple;
17     private alias AliasSeq = TypeTuple;
18 }
19 
20 // Watch out for DMD issue 15483 - Avoiding multiple return statements may help.
21 @safe: /+pragma(inline, true):+/
22 
23 // TODO: Should this module accept SafeInt or SmartInt operands? It could theoretically do so without importing checkedint:
24 /+private @property pure nothrow @nogc {
25     ref N bscal(N)(return ref N num)
26         if(isScalarType!N)
27     {
28         return num;
29     }
30     N bscal(N)(const N num)
31         if(isScalarType!N)
32     {
33         return num;
34     }
35 }
36 private alias BasicScalar(T) = typeof(function() {
37     static if(__traits(compiles, T.init.bscal))
38         return T.init.bscal;
39     else
40         return;
41 }());
42 +/
43 
44 private {
45     /+pragma(inline, true)+/
46     void cmpTypeCheck(N, M)() {
47         static assert(isBoolean!N == isBoolean!M,
48             "The intent of a direct comparison of " ~
49             N.stringof ~ " with " ~ M.stringof ~
50             " is unclear. Add an explicit cast."
51         );
52 
53         alias OT = OpType!(N, "+", M);
54         static assert(isFloatingPoint!OT || isSigned!OT || !(isSigned!N || isSigned!M),
55             "The built-in signed:unsigned comparisons of " ~ N.stringof ~ " to " ~ M.stringof ~
56             " are unsafe. Use an explicit cast, or switch to smartOp/SmartInt."
57         );
58     }
59 }
60 bool cmp(string op, N, M)(const N left, const M right)
61     if(isScalarType!N && isScalarType!M)
62 {
63     cmpTypeCheck!(N, M)();
64     return mixin("left " ~ op ~ " right");
65 }
66 int cmp(N, M)(const N left, const M right)
67     if(isScalarType!N && isScalarType!M)
68 {
69     cmpTypeCheck!(N, M)();
70 
71     static if(isFloatingPoint!N || isFloatingPoint!M)
72         return std.math.cmp(left, right);
73     else
74         return (left < right)? -1 : (right < left);
75 }
76 
77 N unary(string op, bool throws, N)(const N num)
78     if((isIntegral!N) && op.among!("-", "+", "~"))
79 {
80     static assert(isSigned!N || op != "-",
81         "The built-in unary - operation for " ~ N.stringof ~
82         " is unsafe. Use an explicit cast to a signed type, or switch to smartOp/SmartInt."
83     );
84 
85     static if(op == "-") {
86         static if(is(N == int) || is(N == long)) {
87             bool over = false;
88             const N ret = negs(num, over);
89         } else {
90             const over = (num <= N.min);
91             const N ret = -num;
92         }
93 
94         if(over)
95             IntFlag.posOver.raise!throws();
96 
97         return ret;
98     } else
99         return mixin(op ~ "num");
100 }
101 N unary(string op, N)(const N num) pure
102     if((isIntegral!N) && op.among!("-", "+", "~"))
103 {
104     return unary!(op, true)(num);
105 }
106 /+ref+/ N unary(string op, bool throws, N)(/+return+/ ref N num)
107     if((isIntegral!N) && op.among!("++", "--"))
108 {
109     static if(op == "++") {
110         if(num >= N.max)
111             IntFlag.posOver.raise!throws();
112     } else
113     static if(op == "--") {
114         if(num <= N.min)
115             IntFlag.negOver.raise!throws();
116     }
117 
118     return mixin(op ~ "num");
119 }
120 /+ref+/ N unary(string op, N)(/+return+/ ref N num) pure
121     if((isIntegral!N) && op.among("++", "--"))
122 {
123     return unary!(op, true)(num);
124 }
125 
126 N abs(bool throws, N)(const N num)
127     if(isIntegral!N || isBoolean!N)
128 {
129     static if(isSigned!N) {
130         if(num < 0)
131             return unary!("-", throws)(num);
132     }
133     return num;
134 }
135 N abs(N)(const N num) pure
136     if(isIntegral!N || isBoolean!N)
137 {
138     return abs!true(num);
139 }
140 
141 int bsf(bool throws, N)(const N num)
142     if(isFixedPoint!N)
143 {
144     return bsfImpl!throws(num);
145 }
146 int bsf(N)(const N num)
147     if(isFixedPoint!N)
148 {
149     return bsfImpl!true(num);
150 }
151 int bsr(bool throws, N)(const N num)
152     if(isFixedPoint!N)
153 {
154     return bsrImpl!throws(num);
155 }
156 int bsr(N)(const N num)
157     if(isFixedPoint!N)
158 {
159     return bsrImpl!true(num);
160 }
161 
162 int ilogb(bool throws, N)(const N num)
163     if(isFixedPoint!N)
164 {
165     static if(isSigned!N)
166         const absN = cast(Unsigned!N) (num < 0? -num : num);
167     else
168         alias absN = num;
169 
170     return bsrImpl!throws(absN);
171 }
172 int ilogb(N)(const N num)
173     if(isFixedPoint!N)
174 {
175     return ilogb!true(num);
176 }
177 
178 private auto binaryImpl(string op, bool throws, N, M)(const N left, const M right)
179     if(isFixedPoint!N && isFixedPoint!M)
180 {
181     enum wop = (op[$-1] == '=')? op[0 .. $-1] : op;
182     alias P = OpType!(N, wop, M);
183     alias R = Select!(wop == op, P, N);
184 
185     static if(wop.among!("+", "-", "*")) {
186         enum isPromSafe = !(isSigned!N || isSigned!M) || (isSigned!P && isSigned!R);
187         enum needCheck = (wop == "*")?
188             (precision!N + precision!M) > precision!P :
189             (max(precision!N, precision!M) + 1) > precision!P;
190 
191         static if(needCheck) {
192             enum cx = (staticIndexOf!(wop, "+", "-", "*") << 1) + isSigned!P;
193             alias cop = AliasSeq!(addu, adds, subu, subs, mulu, muls)[cx];
194 
195             bool invalid = false;
196             const pret = cop(cast(P)left, cast(P)right, invalid);
197 
198             if(invalid)
199                 IntFlag.over.raise!throws();
200         } else
201             const pret = mixin("left " ~ wop ~ " right");
202 
203         return pret.toImpl!(R, throws);
204     } else
205     static if(wop.among!("/", "%")) {
206         enum isPromSafe = !(isSigned!N || isSigned!M) || (isSigned!P && (wop == "%"? (isSigned!R || !isSigned!N) : isSigned!R));
207 
208         const div0 = (right == 0);
209         static if(isSigned!N && isSigned!M)
210             const posOver = (left == R.min) && (right == -1);
211         else
212             enum posOver = false;
213 
214         if(div0 || posOver) {
215             (posOver? IntFlag.posOver : IntFlag.div0).raise!throws();
216             return cast(R)0; // Prevent unrecoverable FPE
217         } else
218             return cast(R)mixin("left " ~ wop ~ " right");
219     } else
220     static if(wop.among!("<<", ">>", ">>>")) {
221         enum isPromSafe = !isSigned!N || isSigned!R || (op == ">>>");
222 
223         enum invalidSh = ~cast(M)(8 * P.sizeof - 1);
224         if(right & invalidSh)
225             IntFlag.undef.raise!throws();
226 
227         return cast(R) mixin("cast(P)left " ~ wop ~ " right");
228     } else
229     static if(wop.among!("&", "|", "^")) {
230         enum isPromSafe = true;
231 
232         return cast(R)mixin("left " ~ wop ~ " right");
233     } else
234         static assert(false);
235 
236     static assert(isPromSafe,
237         "The built-in " ~ N.stringof ~ " " ~ op ~ " " ~ M.stringof ~
238         " operation is unsafe, due to a signed:unsigned mismatch. Use an explicit cast, or switch to smartOp/SmartInt."
239     );
240 }
241 OpType!(N, op, M) binary(string op, bool throws, N, M)(const N left, const M right)
242     if(isFixedPoint!N && isFixedPoint!M && op.among!("+", "-", "*", "/", "%", "^^", "<<", ">>", ">>>", "&", "|", "^"))
243 {
244     static assert(op != "^^",
245         "pow() should be used instead of operator ^^ because of issue 15288.");
246 
247     return binaryImpl!(op, throws)(left, right);
248 }
249 OpType!(N, op, M) binary(string op, N, M)(const N left, const M right) pure
250     if(isFixedPoint!N && isFixedPoint!M && op.among!("+", "-", "*", "/", "%", "^^", "<<", ">>", ">>>", "&", "|", "^"))
251 {
252     return binary!(op, true)(left, right);
253 }
254 /+ref+/ N binary(string op, bool throws, N, M)(/+return+/ ref N left, const M right)
255     if(isIntegral!N && isFixedPoint!M && (op[$ - 1] == '='))
256 {
257     static assert(op != "^^=",
258         "pow() should be used instead of operator ^^= because of issue 15412.");
259 
260     left = binaryImpl!(op, throws)(left, right);
261     return left;
262 }
263 /+ref+/ N binary(string op, N, M)(/+return+/ ref N left, const M right) pure
264     if(isIntegral!N && isFixedPoint!M && (op[$ - 1] == '='))
265 {
266     return binary!(op, true)(left, right);
267 }
268 
269 auto mulPow2(N, M)(const N coef, const M exp) pure nothrow @nogc
270     if((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M))
271 {
272     return byPow2Impl!("*", NumFromScal!N, NumFromScal!M)(coef, exp);
273 }
274 auto mulPow2(bool throws, N, M)(const N coef, const M exp)
275     if(isFixedPoint!N && isFixedPoint!M)
276 {
277     return byPow2Impl!("*", throws, NumFromScal!N, NumFromScal!M)(coef, exp);
278 }
279 auto mulPow2(N, M)(const N coef, const M exp) pure
280     if(isFixedPoint!N && isFixedPoint!M)
281 {
282     return mulPow2!true(coef, exp);
283 }
284 auto divPow2(N, M)(const N coef, const M exp) pure nothrow @nogc
285     if((isFloatingPoint!N && isScalarType!M) || (isScalarType!N && isFloatingPoint!M))
286 {
287     return byPow2Impl!("/", NumFromScal!N, NumFromScal!M)(coef, exp);
288 }
289 auto divPow2(bool throws, N, M)(const N coef, const M exp)
290     if(isFixedPoint!N && isFixedPoint!M)
291 {
292     return byPow2Impl!("/", throws, NumFromScal!N, NumFromScal!M)(coef, exp);
293 }
294 auto divPow2(N, M)(const N coef, const M exp) pure
295     if(isFixedPoint!N && isFixedPoint!M)
296 {
297     return divPow2!true(coef, exp);
298 }
299 
300 CallType!(std.math.pow, N, M) pow(bool throws, N, M)(const N base, const M exp)
301     if(isFixedPoint!N && isFixedPoint!M)
302 {
303     alias R = typeof(return);
304     static assert(!isSigned!N || isSigned!R,
305         "std.math.pow(" ~ N.stringof ~ ", " ~ M.stringof ~
306         ") is unsafe, due to a signed:unsigned mismatch. Use an explicit cast, or switch to smartOp/SmartInt."
307     );
308 
309     IntFlag flag;
310     const ret = powImpl!(R, Select!(isSigned!M, long, ulong))(base, exp, flag);
311     static assert(is(typeof(ret) == const(R)));
312     if(exp < 0)
313         flag = IntFlag.undef;
314 
315     if(!flag.isNull)
316         flag.raise!throws();
317     return ret;
318 }
319 CallType!(std.math.pow, N, M) pow(N, M)(const N base, const M exp) pure
320     if(isFixedPoint!N && isFixedPoint!M)
321 {
322     return pow!true(left, right);
323 }