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 }