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