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 }