1 /******************************************************************************* 2 3 Struct emulating a large (bigger than ulong) non-negative integer of fixed 4 range (defined via template argument). `ulong` but still fixed in size 5 (defined via template argument). 6 7 Copyright: 8 Copyright (c) 2017 dunnhumby Germany GmbH. 9 All rights reserved. 10 11 License: 12 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 13 Alternatively, this file may be distributed under the terms of the Tango 14 3-Clause BSD License (see LICENSE_BSD.txt for details). 15 16 *******************************************************************************/ 17 18 module ocean.math.WideUInt; 19 20 import ocean.meta.types.Qualifiers; 21 import ocean.core.Test; 22 import ocean.core.TypeConvert: assumeUnique; 23 import ocean.core.Enforce; 24 import ocean.core.Verify; 25 26 import core.stdc.math; 27 import ocean.math.IEEE : feqrel; 28 29 /******************************************************************************* 30 31 Struct emulating a large (bigger than ulong) non-negative integer of fixed 32 range (defined via template argument). `ulong` but still fixed in size 33 (defined via template argument). 34 35 Such struct is a value type with stable binary layout for a given size which 36 is the primary goal of this implementation. Performance may be eventually 37 improved if necessity arises, right now implementation is intentionally 38 simplistic and naive. 39 40 Internally wide integer is represented by a static array of `uint` values 41 such that concatenating their binary representation together results in long 42 binary sequence representing actual stored number. 43 44 Params: 45 N = amount of uint-size words to use as a backing storage for the 46 number 47 48 *******************************************************************************/ 49 50 public struct WideUInt ( size_t N ) 51 { 52 static assert ( 53 N > 2, 54 "For 'N <= 2' using 'uint' or 'ulong' directly is suggested" 55 ); 56 57 /*************************************************************************** 58 59 Binary representation of the number can be formed by putting binary 60 representation of each individual word in `payload` side by 61 side starting from the `payload[$-1]`. 62 63 `uint` is chosen as individual word type because multiplying two `uint` 64 words will result in `ulong` at most, making possible to use basic 65 integer arithmetic to implement most of wide integer arithmetic. 66 67 ***************************************************************************/ 68 69 private uint[N] payload; 70 71 /*********************************************************************** 72 73 Constructor from a regular ulong 74 75 ***********************************************************************/ 76 77 this ( ulong value ) 78 { 79 this.assign(value); 80 } 81 82 /*************************************************************************** 83 84 Returns: 85 amount of meaningful decimal digits in currently stored value 86 87 ***************************************************************************/ 88 89 public size_t decimal_digits ( ) 90 { 91 if (this.opEquals(0)) 92 return 1; 93 94 WideUInt copy = this; 95 96 size_t count; 97 while (copy != 0) 98 { 99 copy.divideBy(10); 100 ++count; 101 } 102 return count; 103 } 104 105 unittest 106 { 107 WideUInt num = 0; 108 test!("==")(num.decimal_digits(), 1); 109 110 num = 42; 111 test!("==")(num.decimal_digits(), 2); 112 113 num = ulong.max; 114 test!("==")(num.decimal_digits(), 20); 115 116 num.multiplyBy(100); 117 test!("==")(num.decimal_digits(), 22); 118 } 119 120 /*************************************************************************** 121 122 Inefficient allocating string conversion useful for tests and 123 prototyping. 124 125 Returns: 126 string representation 127 128 ***************************************************************************/ 129 130 public istring toString ( ) 131 { 132 mstring result; 133 this.toString((cstring s) { result ~= s; }); 134 return assumeUnique(result); 135 } 136 137 unittest 138 { 139 WideUInt num = 0; 140 test!("==")(num.toString(), "0"); 141 142 num = 42; 143 test!("==")(num.toString(), "42"); 144 145 num.payload[2] = 1; 146 test!("==")(num.toString(), "18446744073709551658"); 147 148 num = 0; 149 num.payload[2] = 1; 150 test!("==")(num.toString(), "18446744073709551616"); 151 } 152 153 /*************************************************************************** 154 155 Sink based string conversion 156 157 Params: 158 sink = delegate to call with resulting string 159 160 ***************************************************************************/ 161 162 public void toString ( scope void delegate (cstring) sink ) 163 { 164 auto n = this.decimal_digits(); 165 static mstring buffer; 166 buffer.length = n; 167 assumeSafeAppend(buffer); 168 169 WideUInt copy = this; 170 for (ptrdiff_t idx = n-1; idx >= 0; --idx) 171 { 172 auto remainder = copy.divideBy(10); 173 buffer[idx] = cast(char) ('0' + remainder); 174 } 175 176 sink(buffer); 177 } 178 179 /*************************************************************************** 180 181 Enables assignment from a plain integer type 182 183 Params: 184 rhs = value to assign 185 186 ***************************************************************************/ 187 188 public void opAssign ( ulong rhs ) 189 { 190 this.assign(rhs); 191 } 192 193 unittest 194 { 195 WideUInt i; 196 i = 42; 197 test!("==")(i.payload[0], 42); 198 i = ulong.max; 199 test!("==")(i.payload[0], uint.max); 200 test!("==")(i.payload[1], uint.max); 201 } 202 203 unittest 204 { 205 WideUInt a = 42; 206 WideUInt b = 43; 207 a = b; 208 test!("==")(a, 43); 209 } 210 211 /*************************************************************************** 212 213 Enables equality comparison with a plain integer type 214 215 Params: 216 rhs = value to compare to 217 218 Returns: 219 value as defined by `opEquals` spec 220 221 ***************************************************************************/ 222 223 public equals_t opEquals ( ulong rhs ) 224 { 225 if (this.payload[0] != (rhs & uint.max)) 226 return false; 227 228 if (this.payload[1] != (rhs >> 32)) 229 return false; 230 231 foreach (ref word; this.payload[2 .. N]) 232 { 233 if (word != 0) 234 return false; 235 } 236 237 return true; 238 } 239 240 unittest 241 { 242 WideUInt i; 243 244 i = 42; 245 test!("==")(i, 42); 246 247 i = ulong.max / 2; 248 test!("==")(i, ulong.max / 2); 249 test!("!=")(i, ulong.max); 250 251 i.payload[$-1] = 42; 252 test!("!=")(i, 42); 253 254 i.payload[0] = 0; 255 test!("!=")(i, 42); 256 } 257 258 /*************************************************************************** 259 260 Enables equality comparison with WideUInt of the same size 261 262 Params: 263 rhs = value to compare against 264 265 Returns: 266 value as defined by `opEquals` spec 267 268 ***************************************************************************/ 269 270 public equals_t opEquals ( WideUInt rhs ) 271 { 272 return this.payload[] == rhs.payload[]; 273 } 274 275 unittest 276 { 277 WideUInt a, b; 278 test!("==")(a, b); 279 foreach (size_t i, ref elem; a.payload) 280 { 281 a.payload[i] = 1337; 282 b.payload[i] = 1337; 283 } 284 test!("==")(a, b); 285 a.payload[$-1] = 0; 286 test!("!=")(a, b); 287 } 288 289 /*************************************************************************** 290 291 Enables ordering comparison with WideUInt of the same size 292 293 Params: 294 rhs = value to compare against 295 296 Returns: 297 value as defined by `opCmp` spec 298 299 ***************************************************************************/ 300 301 public int opCmp ( const typeof(this) rhs ) const 302 { 303 ptrdiff_t idx = N-1; 304 while (idx > 0) 305 { 306 if (this.payload[idx] != 0 || rhs.payload[idx] != 0) 307 break; 308 --idx; 309 } 310 311 auto a = this.payload[idx]; 312 auto b = rhs.payload[idx]; 313 return a < b ? -1 : (a > b ? 1 : 0); 314 } 315 316 unittest 317 { 318 WideUInt num1, num2; 319 num1.payload[1] = 1; 320 num2.payload[2] = 1; 321 test(num1 < num2); 322 test(num2 > num1); 323 } 324 325 /*************************************************************************** 326 327 Increment current value by one fitting in uint 328 329 Params: 330 op = operation to perform 331 rhs = value to increment with. Limited to `uint` for now to simplify 332 internal arithmetic. 333 334 Throws: 335 WideUIntRangeException if this WideUInt value range was to be 336 exceeded 337 338 ***************************************************************************/ 339 340 public void opOpAssign ( string op ) ( uint rhs ) if (op == "+") 341 { 342 if (add(this.payload[0], rhs)) 343 enforce(.wideint_exception, this.checkAndInc(1)); 344 } 345 346 unittest 347 { 348 WideUInt num = 0; 349 for (auto i = 0; i < 1000; ++i) 350 num += (1 << 31); 351 test!("==")(num.payload[0], 0); 352 test!("==")(num.payload[1], 500); 353 } 354 355 /*************************************************************************** 356 357 Mutates current number in-place, dividing it by `rhs` and calculating 358 remainder. 359 360 Params: 361 rhs = number to divide by 362 363 Returns: 364 remainder of division 365 366 ***************************************************************************/ 367 368 public uint divideBy ( uint rhs ) 369 out (remainder) 370 { 371 assert(remainder < rhs); 372 } 373 do 374 { 375 ulong remainder = 0; 376 377 for (ptrdiff_t idx = this.payload.length - 1; idx >= 0; --idx) 378 { 379 remainder = (remainder << 32) + this.payload[idx]; 380 ulong result = remainder / rhs; 381 remainder -= rhs * result; 382 verify(result <= uint.max); 383 this.payload[idx] = cast(uint) result; 384 } 385 386 verify(remainder <= uint.max); 387 return cast(uint) remainder; 388 } 389 390 unittest 391 { 392 WideUInt num = 56 * 47; 393 test!("==")(0, num.divideBy(47)); 394 test!("==")(num.payload[0], 56); 395 396 num.payload[0] = 1337; 397 num.payload[1] = 100; 398 test!("==")(1337, num.divideBy(1 << 31)); 399 test!("==")(num.payload[0], 200); 400 } 401 402 /*************************************************************************** 403 404 Mutates this number in place multiplying it by a regular integer 405 406 Params: 407 rhs = value to multiply by 408 409 Throws: 410 WideUIntRangeException if resulting value can't fit in current 411 WideUInt value range 412 413 ***************************************************************************/ 414 415 public void multiplyBy ( uint rhs ) 416 { 417 ulong overflow = 0; 418 419 for (size_t i = 0; i < this.payload.length; ++i) 420 { 421 overflow += cast(ulong)(this.payload[i]) * rhs; 422 this.payload[i] = cast(uint) (overflow & uint.max); 423 overflow >>= 32; 424 } 425 426 enforce(.wideint_exception, overflow == 0); 427 } 428 429 unittest 430 { 431 WideUInt num = 1_000_000_000; 432 num.multiplyBy(1_000_000_000); 433 test!("==")(num.toString(), "1000000000000000000"); 434 } 435 436 /*************************************************************************** 437 438 Returns: 439 Double precision floating point value which is nearest to this 440 number. Uses the same semantics as conversion from ulong to double. 441 442 ***************************************************************************/ 443 444 public double toDouble ( ) 445 { 446 // find most significant word with non-zero value 447 int idx = this.payload.length-1; 448 while (this.payload[idx] == 0 && idx > 0) 449 --idx; 450 451 // if stored value <= ulong.max, just use plain cast 452 if (idx < 2) 453 { 454 ulong value = this.payload[1]; 455 value <<= 32; 456 value |= this.payload[0]; 457 return cast(double) value; 458 } 459 460 // else calculate floating point value from 3 most significant words 461 double MAXWORD = pow(2.0, 32.0); 462 return ((this.payload[idx] * MAXWORD + this.payload[idx-1]) 463 * MAXWORD + this.payload[idx-2]) 464 * ldexp(1.0, (idx-2)*32); 465 } 466 467 unittest 468 { 469 WideUInt num = 123456789; 470 test!("is")(num.toDouble(), 123_456_789.0); 471 472 for (int i = 0; i < 3; ++i) 473 num.multiplyBy(1_000_000_000); 474 475 test(feqrel(num.toDouble(), 123_456_789e+27) == double.mant_dig); 476 } 477 478 /*************************************************************************** 479 480 Helper utility to increment word under index `idx`, calling itself 481 recursively for following words if wraparound happens. 482 483 Params: 484 idx = index of word in the internal payload to increment by 1 485 486 Returns: 487 'false' if whole WideUInt has overflown (== error) 488 489 ***************************************************************************/ 490 491 private bool checkAndInc ( size_t idx ) 492 { 493 if (idx >= N) 494 return false; 495 496 uint after = ++this.payload[idx]; 497 if (after == 0) 498 return this.checkAndInc(idx+1); 499 500 return true; 501 } 502 503 unittest 504 { 505 WideUInt num = uint.max; 506 test(num.checkAndInc(0)); 507 test(num.payload[0] == 0); 508 test(num.payload[1] == 1); 509 510 num.payload[$-1] = uint.max; 511 test(!num.checkAndInc(num.payload.length - 1)); 512 } 513 514 /*************************************************************************** 515 516 Trivial helper to initialize to least significant words of the number 517 from ulong. 518 519 ***************************************************************************/ 520 521 private void assign ( ulong value ) 522 { 523 this.payload[] = 0; 524 this.payload[0] = cast(uint) value; 525 this.payload[1] = cast(uint) (value >> 32); 526 } 527 528 /*************************************************************************** 529 530 Helper utility that adds two uint integers checking for wraparound 531 532 Params: 533 a = reference to integer to increment 534 b = number to add 535 536 Returns: 537 'true' if wraparound/overflow has happenned, 'false' otherwise 538 539 ***************************************************************************/ 540 541 static private bool add ( ref uint a, uint b ) 542 { 543 bool flag; 544 545 version (D_InlineAsm_X86_64) 546 { 547 asm 548 { 549 add dword ptr [RSI], EDI; 550 setb flag; 551 } 552 } 553 else 554 { 555 uint result = a + b; 556 flag = result < a; 557 a = result; 558 } 559 560 return flag; 561 } 562 563 unittest 564 { 565 uint a = uint.max; 566 test( add(a, 1)); 567 test!("==")(a, 0); 568 569 a = 1000; 570 test(!add(a, 1337)); 571 test!("==")(a, 2337UL); 572 573 a = 0; 574 test(!add(a, uint.max)); 575 test!("==")(a, uint.max); 576 } 577 } 578 579 /// 580 unittest 581 { 582 WideUInt!(4) counter; 583 counter = 10000000000000000000U; 584 counter.multiplyBy(10); 585 ++counter; 586 test!("==")(counter.toString(), "100000000000000000001"); 587 } 588 589 unittest 590 { 591 WideUInt!(4) num = ulong.max; 592 test!("==")(num.toString(), "18446744073709551615"); 593 num.multiplyBy(2); 594 test!("==")(num.toString(), "36893488147419103230"); 595 num.divideBy(2); 596 test!("==")(num.toString(), "18446744073709551615"); 597 598 num = 0; 599 num.payload[2] = 1; // ulong.max + 1 600 test!("==")(num.toString(), "18446744073709551616"); 601 } 602 603 /******************************************************************************* 604 605 Exception of this class is thrown when WideUInt overflow is about to happen 606 607 *******************************************************************************/ 608 609 public class WideUIntRangeException : Exception 610 { 611 import ocean.core.Exception : DefaultExceptionCtor; 612 mixin DefaultExceptionCtor; 613 } 614 615 // shared between different WideUInt!(N) instances 616 private static WideUIntRangeException wideint_exception; 617 618 static this ( ) 619 { 620 wideint_exception = new WideUIntRangeException( 621 "Operation would result in WideUInt overflow"); 622 }