1 /++ 2 Note: 3 The module doesn't provide full arithmetic API for now. 4 +/ 5 module mir.bignum.integer; 6 7 import mir.bitop; 8 import mir.serde: serdeProxy, serdeScoped; 9 import mir.utility; 10 import std.traits; 11 12 /++ 13 Stack-allocated big signed integer. 14 Params: 15 maxSize64 = count of 64bit words in coefficient 16 +/ 17 @serdeScoped @serdeProxy!(const(char)[]) 18 struct BigInt(size_t maxSize64) 19 if (maxSize64 && maxSize64 <= ushort.max) 20 { 21 import mir.bignum.low_level_view; 22 import mir.bignum.fixed; 23 24 /// 25 bool sign; 26 /// 27 uint length; 28 /// 29 size_t[ulong.sizeof / size_t.sizeof * maxSize64] data = void; 30 31 /// 32 this(size_t size)(UInt!size fixedInt) 33 { 34 this(fixedInt.data); 35 } 36 37 /// 38 this(size_t N)(size_t[N] data) 39 if (N <= this.data.length) 40 { 41 sign = false; 42 version(LittleEndian) 43 this.data[0 .. N] = data; 44 else 45 this.data[$ - N .. $] = data; 46 length = data.length; 47 normalize; 48 } 49 50 /// 51 this(ulong data) 52 { 53 sign = false; 54 static if (size_t.sizeof == ulong.sizeof) 55 { 56 length = 1; 57 view.leastSignificantFirst[0] = data; 58 } 59 else 60 { 61 length = 2; 62 auto d = view.leastSignificantFirst; 63 d[0] = cast(uint) data; 64 d[1] = cast(uint) (data >> 32); 65 } 66 normalize; 67 } 68 69 /// 70 this()(scope const(char)[] str) @safe pure @nogc 71 // if (isSomeChar!C) 72 { 73 if (fromStringImpl(str)) 74 return; 75 static if (__traits(compiles, () @nogc { throw new Exception("Can't parse BigInt."); })) 76 { 77 import mir.exception: MirException; 78 throw new MirException("Can't parse BigInt!" ~ maxSize64.stringof ~ " from string `", str , "`."); 79 } 80 else 81 { 82 static immutable exception = new Exception("Can't parse BigInt!" ~ maxSize64.stringof ~ "."); 83 throw exception; 84 } 85 } 86 87 /// 88 ref opAssign(ulong data) return 89 { 90 static if (size_t.sizeof == ulong.sizeof) 91 { 92 length = 1; 93 view.leastSignificantFirst[0] = data; 94 } 95 else 96 { 97 length = 2; 98 auto d = view.leastSignificantFirst; 99 d[0] = cast(uint) data; 100 d[1] = cast(uint) (data >> 32); 101 } 102 normalize; 103 return this; 104 } 105 106 static if (maxSize64 == 3) 107 /// 108 version(mir_bignum_test) @safe pure @nogc unittest 109 { 110 import mir.math.constant: PI; 111 BigInt!4 integer = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor 112 assert(integer.sign); 113 integer.sign = false; 114 assert(integer == BigInt!4.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8")); 115 } 116 117 /// 118 ref opAssign(size_t rhsMaxSize64)(auto ref scope const BigInt!rhsMaxSize64 rhs) return 119 if (rhsMaxSize64 < maxSize64) 120 { 121 this.sign = rhs.sign; 122 this.length = rhs.length; 123 version(LittleEndian) 124 { 125 data[0 .. length] = rhs.data[0 .. length]; 126 } 127 else 128 { 129 data[$ - length .. $] = rhs.data[$ - length .. $]; 130 } 131 return this; 132 } 133 134 /++ 135 Returns: false in case of overflow or incorrect string. 136 Precondition: non-empty coefficients. 137 +/ 138 bool fromStringImpl(C)(scope const(C)[] str) 139 scope @trusted pure @nogc nothrow 140 if (isSomeChar!C) 141 { 142 auto work = BigIntView!size_t(data[]); 143 if (work.fromStringImpl(str)) 144 { 145 length = cast(uint) work.coefficients.length; 146 sign = work.sign; 147 return true; 148 } 149 return false; 150 } 151 152 /// 153 BigInt copy() @property 154 { 155 BigInt ret; 156 ret.sign = sign; 157 ret.length = length; 158 ret.data = data; 159 return ret; 160 } 161 162 /// 163 bool opEquals()(auto ref const BigInt rhs) 164 const @safe pure nothrow @nogc 165 { 166 return view == rhs.view; 167 } 168 169 /// 170 bool opEquals()(size_t rhs, bool rhsSign = false) 171 const @safe pure nothrow @nogc 172 { 173 return rhs == 0 && length == 0 || length == 1 && sign == rhsSign && view.unsigned.leastSignificant == rhs; 174 } 175 176 /// 177 bool opEquals()(sizediff_t rhs) 178 const @safe pure nothrow @nogc 179 { 180 auto sign = rhs < 0; 181 return opEquals(sign ? ulong(-rhs) : ulong(rhs), sign); 182 } 183 184 /++ 185 +/ 186 auto opCmp()(auto ref const BigInt rhs) 187 const @safe pure nothrow @nogc 188 { 189 return view.opCmp(rhs.view); 190 } 191 192 /// 193 BigIntView!size_t view()() @property 194 { 195 version (LittleEndian) 196 return typeof(return)(data[0 .. length], sign); 197 else 198 return typeof(return)(data[$ - length .. $], sign); 199 } 200 201 /// 202 BigIntView!(const size_t) view()() const @property 203 { 204 version (LittleEndian) 205 return typeof(return)(data[0 .. length], sign); 206 else 207 return typeof(return)(data[$ - length .. $], sign); 208 } 209 210 /// 211 void normalize()() 212 { 213 auto norm = view.normalized; 214 this.length = cast(uint) norm.unsigned.coefficients.length; 215 this.sign = norm.sign; 216 } 217 218 /++ 219 +/ 220 void putCoefficient(size_t value) 221 { 222 assert(length < data.length); 223 version (LittleEndian) 224 data[length++] = value; 225 else 226 data[$ - ++length] = value; 227 } 228 229 /++ 230 Performs `size_t overflow = (big += overflow) *= scalar` operatrion. 231 Params: 232 rhs = unsigned value to multiply by 233 overflow = initial overflow value 234 Returns: 235 unsigned overflow value 236 +/ 237 size_t opOpAssign(string op : "*")(size_t rhs, size_t overflow = 0u) 238 @safe pure nothrow @nogc 239 { 240 if (length == 0) 241 goto L; 242 overflow = view.unsigned.opOpAssign!op(rhs, overflow); 243 if (overflow && length < data.length) 244 { 245 L: 246 putCoefficient(overflow); 247 overflow = 0; 248 } 249 return overflow; 250 } 251 252 /++ 253 Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. 254 Precondition: `overflow < rhs` 255 Params: 256 rhs = unsigned value to devide by 257 overflow = initial unsigned overflow 258 Returns: 259 unsigned remainder value (evaluated overflow) 260 +/ 261 uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) 262 @safe pure nothrow @nogc 263 { 264 assert(overflow < rhs); 265 if (length) 266 return view.unsigned.opOpAssign!op(rhs, overflow); 267 return overflow; 268 } 269 270 /++ 271 Performs `size_t overflow = (big += overflow) *= fixed` operatrion. 272 Params: 273 rhs = unsigned value to multiply by 274 overflow = initial overflow value 275 Returns: 276 unsigned overflow value 277 +/ 278 UInt!size opOpAssign(string op : "*", size_t size)(UInt!size rhs, UInt!size overflow = 0) 279 @safe pure nothrow @nogc 280 { 281 if (length == 0) 282 goto L; 283 overflow = view.unsigned.opOpAssign!op(rhs, overflow); 284 if (overflow && length < data.length) 285 { 286 L: 287 static if (size <= 64) 288 { 289 auto o = cast(ulong)overflow; 290 static if (size_t.sizeof == ulong.sizeof) 291 { 292 putCoefficient(o); 293 overflow = UInt!size.init; 294 } 295 else 296 { 297 putCoefficient(cast(uint)o); 298 o >>= 32; 299 if (length < data.length) 300 { 301 putCoefficient(cast(uint)o); 302 o = 0; 303 } 304 overflow = UInt!size(o); 305 } 306 } 307 else 308 { 309 do 310 { 311 putCoefficient(cast(size_t)overflow); 312 overflow >>= size_t.sizeof * 8; 313 } 314 while(overflow && length < data.length); 315 } 316 } 317 return overflow; 318 } 319 320 /++ 321 Performs `size_t overflow = big *= fixed` operatrion. 322 Params: 323 rhs = unsigned value to multiply by 324 Returns: 325 overflow 326 +/ 327 bool opOpAssign(string op, size_t rhsMaxSize64)(ref const BigInt!rhsMaxSize64 rhs) 328 @safe pure nothrow @nogc 329 if (op == "+" || op == "-") 330 { 331 return opOpAssign!op(rhs.view); 332 } 333 334 /// ditto 335 bool opOpAssign(string op)(BigIntView!(const size_t) rhs) 336 @safe pure nothrow @nogc 337 if (op == "+" || op == "-") 338 { 339 sizediff_t diff = length - rhs.coefficients.length; 340 if (diff < 0) 341 { 342 auto oldLength = length; 343 length = cast(int)rhs.coefficients.length; 344 view.unsigned.leastSignificantFirst[oldLength .. $] = 0; 345 } 346 else 347 if (rhs.coefficients.length == 0) 348 return false; 349 auto thisView = view; 350 auto overflow = thisView.opOpAssign!op(rhs); 351 this.sign = thisView.sign; 352 if (overflow) 353 { 354 if (length < data.length) 355 { 356 putCoefficient(overflow); 357 overflow = false; 358 } 359 } 360 else 361 { 362 normalize; 363 } 364 return overflow; 365 } 366 367 /++ 368 +/ 369 static BigInt fromHexString(bool allowUnderscores = false)(scope const(char)[] str) 370 @trusted pure 371 { 372 BigInt ret; 373 if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) 374 return ret; 375 version(D_Exceptions) 376 throw hexStringException; 377 else 378 assert(0, hexStringErrorMsg); 379 } 380 381 /++ 382 +/ 383 bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) 384 @safe pure @nogc nothrow 385 if (isSomeChar!C) 386 { 387 auto work = BigIntView!size_t(data); 388 auto ret = work.fromHexStringImpl!(C, allowUnderscores)(str); 389 if (ret) 390 { 391 length = cast(uint)work.unsigned.coefficients.length; 392 sign = work.sign; 393 } 394 return ret; 395 } 396 397 /// 398 bool mulPow5(size_t degree) 399 { 400 // assert(approxCanMulPow5(degree)); 401 if (length == 0) 402 return false; 403 enum n = MaxWordPow5!size_t; 404 enum wordInit = size_t(5) ^^ n; 405 size_t word = wordInit; 406 bool of; 407 while(degree) 408 { 409 if (degree >= n) 410 { 411 degree -= n; 412 } 413 else 414 { 415 word = 1; 416 do word *= 5; 417 while(--degree); 418 } 419 if (auto overflow = view *= word) 420 { 421 of = length >= data.length; 422 if (!of) 423 putCoefficient(overflow); 424 } 425 } 426 return of; 427 } 428 429 /// 430 ref BigInt opOpAssign(string op)(size_t shift) 431 @safe pure nothrow @nogc return 432 if (op == "<<" || op == ">>") 433 { 434 auto index = shift / (size_t.sizeof * 8); 435 auto bs = shift % (size_t.sizeof * 8); 436 auto ss = size_t.sizeof * 8 - bs; 437 static if (op == ">>") 438 { 439 if (index >= length) 440 { 441 length = 0; 442 return this; 443 } 444 auto d = view.leastSignificantFirst; 445 if (bs) 446 { 447 foreach (j; 0 .. d.length - (index + 1)) 448 { 449 d[j] = (d[j + index] >>> bs) | (d[j + (index + 1)] << ss); 450 } 451 } 452 else 453 { 454 foreach (j; 0 .. d.length - (index + 1)) 455 { 456 d[j] = d[j + index]; 457 } 458 } 459 auto most = d[$ - (index + 1)] = d.back >>> bs; 460 length -= index + (most == 0); 461 } 462 else 463 { 464 if (index >= data.length || length == 0) 465 { 466 length = 0; 467 return this; 468 } 469 470 if (bs) 471 { 472 auto most = view.unsigned.mostSignificant >> ss; 473 length += index; 474 if (length < data.length) 475 { 476 if (most) 477 { 478 length++; 479 view.unsigned.mostSignificant = most; 480 length--; 481 } 482 } 483 else 484 { 485 length = data.length; 486 } 487 488 auto d = view.leastSignificantFirst; 489 foreach_reverse (j; index + 1 .. length) 490 { 491 d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); 492 } 493 d[index] = d.front << bs; 494 if (length < data.length) 495 length += most != 0; 496 } 497 else 498 { 499 length = cast(uint) min(length + index, cast(uint)data.length); 500 auto d = view.leastSignificantFirst; 501 foreach_reverse (j; index .. length) 502 { 503 d[j] = d[j - index]; 504 } 505 } 506 view.leastSignificantFirst[0 .. index] = 0; 507 } 508 return this; 509 } 510 511 /// 512 T opCast(T, bool wordNormalized = false, bool nonZero = false)() const 513 if (isFloatingPoint!T && isMutable!T) 514 { 515 return view.opCast!(T, wordNormalized, nonZero); 516 } 517 518 /// 519 T opCast(T, bool nonZero = false)() const 520 if (is(T == long) || is(T == int)) 521 { 522 return this.view.opCast!(T, nonZero); 523 } 524 525 /++ 526 Returns: overflow flag 527 +/ 528 bool copyFrom(W, WordEndian endian)(BigIntView!(const W, endian) view) 529 { 530 static if (W.sizeof > size_t.sizeof && endian == TargetEndian) 531 { 532 return this.copyFrom(cast(BigIntView!(const size_t))view); 533 } 534 else 535 { 536 this.sign = view.sign; 537 auto lhs = BigUIntView!W(cast(W[])data); 538 auto rhs = view; 539 auto overflow = lhs.coefficients.length < rhs.coefficients.length; 540 auto n = overflow ? lhs.coefficients.length : rhs.coefficients.length; 541 lhs.leastSignificantFirst[0 .. n] = rhs.leastSignificantFirst[0 .. n]; 542 this.length = cast(uint)(n / (size_t.sizeof / W.sizeof)); 543 if (auto tail = n % (size_t.sizeof / W.sizeof)) 544 { 545 this.length++; 546 auto shift = ((size_t.sizeof / W.sizeof) - tail) * (W.sizeof * 8); 547 auto value = this.view.unsigned.mostSignificant; 548 value <<= shift; 549 value >>= shift; 550 this.view.unsigned.mostSignificant = value; 551 } 552 return overflow; 553 } 554 } 555 556 /// ditto 557 bool copyFrom(W, WordEndian endian)(BigUIntView!(const W, endian) view) 558 { 559 return this.copyFrom(BigIntView!(const W, endian)(view)); 560 } 561 562 /// 563 immutable(C)[] toString(C = char)() const @safe pure nothrow 564 if(isSomeChar!C && isMutable!C) 565 { 566 C[ceilLog10Exp2(data.length * (size_t.sizeof * 8)) + 1] buffer = void; 567 BigInt copy = this; 568 auto len = copy.view.unsigned.toStringImpl(buffer); 569 if (sign) 570 buffer[$ - ++len] = '-'; 571 return buffer[$ - len .. $].idup; 572 } 573 574 static if (maxSize64 == 3) 575 /// 576 version(mir_bignum_test) @safe pure unittest 577 { 578 auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; 579 auto integer = BigInt!4(str); 580 assert(integer.toString == str); 581 582 integer = BigInt!4.init; 583 assert(integer.toString == "0"); 584 } 585 586 /// 587 void toString(C = char, W)(scope ref W w) const 588 if(isSomeChar!C && isMutable!C) 589 { 590 C[ceilLog10Exp2(data.length * (size_t.sizeof * 8)) + 1] buffer = void; 591 BigInt copy = this; 592 auto len = copy.view.unsigned.toStringImpl(buffer); 593 if (sign) 594 buffer[$ - ++len] = '-'; 595 w.put(buffer[$ - len .. $]); 596 } 597 598 static if (maxSize64 == 3) 599 /// Check @nogc toString impl 600 version(mir_bignum_test) @safe pure @nogc unittest 601 { 602 import mir.format: stringBuf; 603 auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; 604 auto integer = BigInt!4(str); 605 stringBuf buffer; 606 buffer << integer; 607 assert(buffer.data == str, buffer.data); 608 } 609 } 610 611 612 /// 613 version(mir_bignum_test) 614 unittest 615 { 616 import mir.bignum.fixed; 617 import mir.bignum.low_level_view; 618 619 auto a = BigInt!4.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8"); 620 auto b = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); 621 auto c = BigInt!4.fromHexString("7869dd864619cace5953a09910327b3971413e6aa5f417fa25a2ac93291b941f"); 622 c.sign = true; 623 assert(a != b); 624 assert(a < b); 625 a -= b; 626 assert(a.sign); 627 assert(a == c); 628 a -= a; 629 assert(!a.sign); 630 assert(!a.length); 631 632 auto d = BigInt!4.fromHexString("0de1a911c6dc8f90a7169a148e65d22cf34f6a8254ae26362b064f26ac44218a"); 633 assert((b *= 0x7869dd86) == 0x5c019770); 634 assert(b == d); 635 636 d = BigInt!4.fromHexString("856eeb23e68cc73f2a517448862cdc97e83f9dfa23768296724bf00fda7df32a"); 637 auto o = b *= UInt!128.fromHexString("f79a222050aaeaaa417fa25a2ac93291"); 638 assert(o == UInt!128.fromHexString("d6d15b99499b73e68c3331eb0f7bf16")); 639 assert(b == d); 640 641 d = BigInt!4.fromHexString("d"); // initial value 642 d.mulPow5(60); 643 c = BigInt!4.fromHexString("81704fcef32d3bd8117effd5c4389285b05d"); 644 assert(d == c); 645 646 d >>= 80; 647 c = BigInt!4.fromHexString("81704fcef32d3bd8"); 648 assert(d == c); 649 650 c = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); 651 d = BigInt!4.fromHexString("9935cea0707f79a222050aaeaaaed17feb7aa76999d700000000000000000000"); 652 c <<= 80; 653 assert(d == c); 654 c >>= 80; 655 c <<= 84; 656 d <<= 4; 657 assert(d == c); 658 assert(c != b); 659 b.sign = true; 660 assert(!c.copyFrom(b.view)); 661 assert(c == b); 662 b >>= 18; 663 auto bView = cast(BigIntView!ushort)b.view; 664 assert(!c.copyFrom(bView.topLeastSignificantPart(bView.unsigned.coefficients.length - 1))); 665 assert(c == b); 666 } 667 668 version(mir_bignum_test) 669 @safe pure @nogc unittest 670 { 671 BigInt!4 i = "-0"; 672 assert(i.view.coefficients.length == 0); 673 assert(cast(long) i == 0); 674 }