diff --git a/src/bigints.nim b/src/bigints.nim index b00d466..3678a6e 100644 --- a/src/bigints.nim +++ b/src/bigints.nim @@ -5,12 +5,13 @@ import std/[algorithm, bitops, math, options] type + Limb = uint32 BigInt* = object ## An arbitrary precision integer. # Invariants for `a: BigInt`: # * if `a` is non-zero: `a.limbs[a.limbs.high] != 0` # * if `a` is zero: `a.limbs.len <= 1` - limbs: seq[uint32] + limbs: seq[Limb] isNegative: bool @@ -78,8 +79,11 @@ const zero = initBigInt(0) one = initBigInt(1) +func isZeroLimbs(limbs: openArray[Limb]): bool {.inline.} = + limbs.len == 0 or (limbs.len == 1 and limbs[0] == 0) + func isZero(a: BigInt): bool {.inline.} = - a.limbs.len == 0 or (a.limbs.len == 1 and a.limbs[0] == 0) + isZeroLimbs(a.limbs) func abs*(a: BigInt): BigInt = # Returns the absolute value of `a`. @@ -89,22 +93,22 @@ func abs*(a: BigInt): BigInt = result = a result.isNegative = false -func unsignedCmp(a: BigInt, b: uint32): int64 = +func unsignedCmp(aLimbs: openArray[Limb], b: uint32): int64 = # ignores the sign of `a` # `a` and `b` are assumed to not be zero - result = int64(a.limbs.len) - 1 + result = int64(aLimbs.len) - 1 if result != 0: return - result = int64(a.limbs[0]) - int64(b) + result = int64(aLimbs[0]) - int64(b) -func unsignedCmp(a: uint32, b: BigInt): int64 = -unsignedCmp(b, a) +func unsignedCmp(a: uint32, bLimbs: openArray[Limb]): int64 = -unsignedCmp(bLimbs, a) -func unsignedCmp(a, b: BigInt): int64 = +func unsignedCmp(aLimbs, bLimbs: openArray[Limb]): int64 = # ignores the signs of `a` and `b` # `a` and `b` are assumed to not be zero - result = int64(a.limbs.len) - int64(b.limbs.len) + result = int64(aLimbs.len) - int64(bLimbs.len) if result != 0: return - for i in countdown(a.limbs.high, 0): - result = int64(a.limbs[i]) - int64(b.limbs[i]) + for i in countdown(aLimbs.high, 0): + result = int64(aLimbs[i]) - int64(bLimbs[i]) if result != 0: return @@ -124,12 +128,12 @@ func cmp(a, b: BigInt): int64 = if b.isZero or not b.isNegative: return -1 else: - return unsignedCmp(b, a) + return unsignedCmp(b.limbs, a.limbs) else: # a > 0 if b.isZero or b.isNegative: return 1 else: - return unsignedCmp(a, b) + return unsignedCmp(a.limbs, b.limbs) func cmp(a: BigInt, b: int32): int64 = ## Returns: @@ -140,14 +144,14 @@ func cmp(a: BigInt, b: int32): int64 = return -b.int64 elif a.isNegative: if b < 0: - return unsignedCmp((not b).uint32 + 1, a) + return unsignedCmp((not b).uint32 + 1, a.limbs) else: return -1 else: # a > 0 if b <= 0: return 1 else: - return unsignedCmp(a, b.uint32) + return unsignedCmp(a.limbs, b.uint32) func cmp(a: int32, b: BigInt): int64 = -cmp(b, a) @@ -191,33 +195,33 @@ template addParts(toAdd) = a.limbs[i] = uint32(tmp and uint32.high) tmp = tmp shr 32 -func unsignedAdditionInt(a: var BigInt, b: BigInt, c: uint32) = - let bl = b.limbs.len +func unsignedAdditionInt(a: var BigInt, bLimbs: openArray[Limb], c: uint32) = + let bl = bLimbs.len a.limbs.setLen(bl) var tmp: uint64 = uint64(c) for i in 0 ..< bl: - addParts(uint64(b.limbs[i])) + addParts(uint64(bLimbs[i])) if tmp > 0'u64: a.limbs.add(uint32(tmp)) a.isNegative = false -func unsignedAddition(a: var BigInt, b, c: BigInt) = +func unsignedAddition(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) = let - bl = b.limbs.len - cl = c.limbs.len + bl = bLimbs.len + cl = cLimbs.len var m = min(bl, cl) a.limbs.setLen(max(bl, cl)) var tmp = 0'u64 for i in 0 ..< m: - addParts(uint64(b.limbs[i]) + uint64(c.limbs[i])) + addParts(uint64(bLimbs[i]) + uint64(cLimbs[i])) if bl < cl: for i in m ..< cl: - addParts(uint64(c.limbs[i])) + addParts(uint64(cLimbs[i])) else: for i in m ..< bl: - addParts(uint64(b.limbs[i])) + addParts(uint64(bLimbs[i])) if tmp > 0'u64: a.limbs.add(uint32(tmp)) a.isNegative = false @@ -236,14 +240,14 @@ func `-`*(a: BigInt): BigInt = result = a negate(result) -template realUnsignedSubtractionInt(a: var BigInt, b: BigInt, c: uint32) = +template realUnsignedSubtractionInt(a: var BigInt, bLimbs: openArray[Limb], c: uint32) = # b > c - let bl = b.limbs.len + let bl = bLimbs.len a.limbs.setLen(bl) var tmp = int64(c) for i in 0 ..< bl: - tmp = int64(uint32.high) + 1 + int64(b.limbs[i]) - tmp + tmp = int64(uint32.high) + 1 + int64(bLimbs[i]) - tmp a.limbs[i] = uint32(tmp and int64(uint32.high)) tmp = 1 - (tmp shr 32) a.isNegative = false @@ -251,28 +255,28 @@ template realUnsignedSubtractionInt(a: var BigInt, b: BigInt, c: uint32) = normalize(a) assert tmp == 0 -template realUnsignedSubtraction(a: var BigInt, b, c: BigInt) = +template realUnsignedSubtraction(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) = # b > c let - bl = b.limbs.len - cl = c.limbs.len + bl = bLimbs.len + cl = cLimbs.len var m = min(bl, cl) a.limbs.setLen(max(bl, cl)) var tmp = 0'i64 for i in 0 ..< m: - tmp = int64(uint32.high) + 1 + int64(b.limbs[i]) - int64(c.limbs[i]) - tmp + tmp = int64(uint32.high) + 1 + int64(bLimbs[i]) - int64(cLimbs[i]) - tmp a.limbs[i] = uint32(tmp and int64(uint32.high)) tmp = 1 - (tmp shr 32) if bl < cl: for i in m ..< cl: - tmp = int64(uint32.high) + 1 - int64(c.limbs[i]) - tmp + tmp = int64(uint32.high) + 1 - int64(cLimbs[i]) - tmp a.limbs[i] = uint32(tmp and int64(uint32.high)) tmp = 1 - (tmp shr 32) a.isNegative = true else: for i in m ..< bl: - tmp = int64(uint32.high) + 1 + int64(b.limbs[i]) - tmp + tmp = int64(uint32.high) + 1 + int64(bLimbs[i]) - tmp a.limbs[i] = uint32(tmp and int64(uint32.high)) tmp = 1 - (tmp shr 32) a.isNegative = false @@ -280,24 +284,24 @@ template realUnsignedSubtraction(a: var BigInt, b, c: BigInt) = normalize(a) assert tmp == 0 -func unsignedSubtractionInt(a: var BigInt, b: BigInt, c: uint32) = +func unsignedSubtractionInt(a: var BigInt, bLimbs: openArray[Limb], c: uint32) = # `b` is not zero - let cmpRes = unsignedCmp(b, c) + let cmpRes = unsignedCmp(bLimbs, c) if cmpRes > 0: - realUnsignedSubtractionInt(a, b, c) + realUnsignedSubtractionInt(a, bLimbs, c) elif cmpRes < 0: # `b` is only a single limb - a.limbs = @[c - b.limbs[0]] + a.limbs = @[c - bLimbs[0]] a.isNegative = true else: # b == c a = zero -func unsignedSubtraction(a: var BigInt, b, c: BigInt) = - let cmpRes = unsignedCmp(b, c) +func unsignedSubtraction(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) = + let cmpRes = unsignedCmp(bLimbs, cLimbs) if cmpRes > 0: - realUnsignedSubtraction(a, b, c) + realUnsignedSubtraction(a, bLimbs, cLimbs) elif cmpRes < 0: - realUnsignedSubtraction(a, c, b) + realUnsignedSubtraction(a, cLimbs, bLimbs) a.negate() else: # b == c a = zero @@ -308,29 +312,29 @@ func additionInt(a: var BigInt, b: BigInt, c: int32) = a = c.initBigInt elif b.isNegative: if c < 0: - unsignedAdditionInt(a, b, (not c).uint32 + 1) + unsignedAdditionInt(a, b.limbs, (not c).uint32 + 1) else: - unsignedSubtractionInt(a, b, c.uint32) + unsignedSubtractionInt(a, b.limbs, c.uint32) a.negate() else: if c < 0: - unsignedSubtractionInt(a, b, (not c).uint32 + 1) + unsignedSubtractionInt(a, b.limbs, (not c).uint32 + 1) else: - unsignedAdditionInt(a, b, c.uint32) + unsignedAdditionInt(a, b.limbs, c.uint32) func addition(a: var BigInt, b, c: BigInt) = # a = b + c if b.isNegative: if c.isNegative: - unsignedAddition(a, b, c) + unsignedAddition(a, b.limbs, c.limbs) a.isNegative = true else: - unsignedSubtraction(a, c, b) + unsignedSubtraction(a, c.limbs, b.limbs) else: if c.isNegative: - unsignedSubtraction(a, b, c) + unsignedSubtraction(a, b.limbs, c.limbs) else: - unsignedAddition(a, b, c) + unsignedAddition(a, b.limbs, c.limbs) func `+`*(a, b: BigInt): BigInt = ## Addition for `BigInt`s. @@ -356,29 +360,29 @@ func subtractionInt(a: var BigInt, b: BigInt, c: int32) = a = -c.initBigInt elif b.isNegative: if c < 0: - unsignedSubtractionInt(a, b, (not c).uint32 + 1) + unsignedSubtractionInt(a, b.limbs, (not c).uint32 + 1) else: - unsignedAdditionInt(a, b, c.uint32) + unsignedAdditionInt(a, b.limbs, c.uint32) a.negate() else: if c < 0: - unsignedAdditionInt(a, b, (not c).uint32 + 1) + unsignedAdditionInt(a, b.limbs, (not c).uint32 + 1) else: - unsignedSubtractionInt(a, b, c.uint32) + unsignedSubtractionInt(a, b.limbs, c.uint32) func subtraction(a: var BigInt, b, c: BigInt) = # a = b - c if b.isNegative: if c.isNegative: - unsignedSubtraction(a, c, b) + unsignedSubtraction(a, c.limbs, b.limbs) else: - unsignedAddition(a, b, c) + unsignedAddition(a, b.limbs, c.limbs) a.isNegative = true else: if c.isNegative: - unsignedAddition(a, b, c) + unsignedAddition(a, b.limbs, c.limbs) else: - unsignedSubtraction(a, b, c) + unsignedSubtraction(a, b.limbs, c.limbs) func `-`*(a, b: BigInt): BigInt = ## Subtraction for `BigInt`s. @@ -398,17 +402,16 @@ template `-=`*(a: var BigInt, b: BigInt) = assert a == 3.initBigInt a = a - b - -func unsignedMultiplication(a: var BigInt, b, c: BigInt) {.inline.} = +func unsignedLongMultiplication(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) {.inline.} = # always called with bl >= cl let - bl = b.limbs.len - cl = c.limbs.len + bl = bLimbs.len + cl = cLimbs.len a.limbs.setLen(bl + cl) var tmp = 0'u64 for i in 0 ..< bl: - tmp += uint64(b.limbs[i]) * uint64(c.limbs[0]) + tmp += uint64(bLimbs[i]) * uint64(cLimbs[0]) a.limbs[i] = uint32(tmp and uint32.high) tmp = tmp shr 32 @@ -417,7 +420,7 @@ func unsignedMultiplication(a: var BigInt, b, c: BigInt) {.inline.} = for j in 1 ..< cl: tmp = 0'u64 for i in 0 ..< bl: - tmp += uint64(a.limbs[j + i]) + uint64(b.limbs[i]) * uint64(c.limbs[j]) + tmp += uint64(a.limbs[j + i]) + uint64(bLimbs[i]) * uint64(cLimbs[j]) a.limbs[j + i] = uint32(tmp and uint32.high) tmp = tmp shr 32 var pos = j + bl @@ -428,19 +431,109 @@ func unsignedMultiplication(a: var BigInt, b, c: BigInt) {.inline.} = inc pos normalize(a) -func multiplication(a: var BigInt, b, c: BigInt) = +func unsignedMultiplicationInt(a: var BigInt, bLimbs: openArray[Limb], c: uint32) {.inline.} = + if c == 0: + a = zero + return + let + bl = bLimbs.len + a.limbs.setLen(bl + 1) + var tmp = 0'u64 + let c = uint64(c) + + for i in 0 ..< bl: + tmp += uint64(bLimbs[i]) * c + a.limbs[i] = uint32(tmp and uint32.high) + tmp = tmp shr 32 + + a.limbs[bl] = uint32(tmp) + normalize(a) + +const + karatsubaThreshold = 80 + +func unsignedKaratsubaMultiplication(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) + +func unsignedMultiplication(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) = # a = b * c - if b.isZero or c.isZero: + if bLimbs.isZeroLimbs or cLimbs.isZeroLimbs: a = zero return let - bl = b.limbs.len - cl = c.limbs.len + bl = bLimbs.len + cl = cLimbs.len if cl > bl: - unsignedMultiplication(a, c, b) + if bl >= karatsubaThreshold: + unsignedKaratsubaMultiplication(a, cLimbs, bLimbs) + else: + unsignedLongMultiplication(a, cLimbs, bLimbs) + else: + if cl >= karatsubaThreshold: + unsignedKaratsubaMultiplication(a, bLimbs, cLimbs) + else: + unsignedLongMultiplication(a, bLimbs, cLimbs) + +func multiplication(a: var BigInt, b, c: BigInt) = + unsignedMultiplication(a, b.limbs, c.limbs) + a.isNegative = b.isNegative xor c.isNegative + +func `shl`*(x: BigInt, y: Natural): BigInt +func `shr`*(x: BigInt, y: Natural): BigInt + +template toOpenArrayCompat(a: typed, b, c: int): untyped = + when (NimMajor, NimMinor, NimPatch) >= (1, 6, 10): + a.toOpenArray(b, c) else: - unsignedMultiplication(a, b, c) + when nimvm: + a[b .. c] + else: + a.toOpenArray(b, c) + +func unsignedKaratsubaMultiplication(a: var BigInt, bLimbs, cLimbs: openArray[Limb]) = + if bLimbs.isZeroLimbs or cLimbs.isZeroLimbs: + a = zero + return + let + bl = bLimbs.len + cl = cLimbs.len + n = min(bl, cl) + k = n shr 1 + if bl == 1: + # base case : multiply the only limb with each limb of second term + unsignedMultiplicationInt(a, cLimbs, bLimbs[0]) + return + if cl == 1: + unsignedMultiplicationInt(a, bLimbs, cLimbs[0]) + return + if bl < karatsubaThreshold or cl < karatsubaThreshold: + if bl >= cl: + unsignedLongMultiplication(a, bLimbs, cLimbs) + else: + unsignedLongMultiplication(a, cLimbs, bLimbs) + return + # Decompose `b` and `c` in two parts of (almost) equal length + template low_b: openArray[Limb] = bLimbs.toOpenArrayCompat(0, k-1) + template high_b: openArray[Limb] = bLimbs.toOpenArrayCompat(k, bl-1) + template low_c: openArray[Limb] = cLimbs.toOpenArrayCompat(0, k-1) + template high_c: openArray[Limb] = cLimbs.toOpenArrayCompat(k, cl-1) + + # subtractive version of Karatsuba's algorithm to limit carry handling + var lowProduct, highProduct, add3, add4, add5, middleTerm: BigInt = zero + + unsignedMultiplication(lowProduct, low_b, low_c) + unsignedMultiplication(highProduct, high_b, high_c) + + unsignedSubtraction(add3, low_b, high_b) + unsignedSubtraction(add4, high_c, low_c) + + multiplication(add5, add4, add3) + + middleTerm = lowProduct + highProduct + add5 + a = lowProduct + middleTerm shl (32*k) + highProduct shl (64*k) + +func karatsubaMultiplication*(a: var BigInt, b, c: BigInt) = + unsignedKaratsubaMultiplication(a, b.limbs, c.limbs) a.isNegative = b.isNegative xor c.isNegative func `*`*(a, b: BigInt): BigInt = @@ -1150,7 +1243,7 @@ func initBigInt*(str: string, base: range[2..36] = 10): BigInt = if str[j] != '_': let digit = parseDigit(str[j], base) num = (num * base) + digit - unsignedAdditionInt(result, result * d, num) + unsignedAdditionInt(result, (result * d).limbs, num) else: # iterator over a block smaller than `size`, so we have to compute `mul` var mul = 1'u32 # the multiplication factor for num @@ -1159,7 +1252,7 @@ func initBigInt*(str: string, base: range[2..36] = 10): BigInt = let digit = parseDigit(str[j], base) num = (num * base) + digit mul *= base - unsignedAdditionInt(result, result * initBigInt(mul), num) + unsignedAdditionInt(result, (result * initBigInt(mul)).limbs, num) result.isNegative = neg @@ -1235,7 +1328,6 @@ iterator `..<`*(a, b: BigInt): BigInt = yield res inc res - func modulo(a, modulus: BigInt): BigInt = ## Like `mod`, but the result is always in the range `[0, modulus-1]`. ## `modulus` should be greater than zero. @@ -1251,7 +1343,6 @@ func fastLog2*(a: BigInt): int = return -1 bitops.fastLog2(a.limbs[^1]) + 32*(a.limbs.high) - func invmod*(a, modulus: BigInt): BigInt = ## Compute the modular inverse of `a` modulo `modulus`. ## The return value is always in the range `[1, modulus-1]` diff --git a/tests/bench_multiplication.nim b/tests/bench_multiplication.nim new file mode 100644 index 0000000..3d502d9 --- /dev/null +++ b/tests/bench_multiplication.nim @@ -0,0 +1,44 @@ +import bigints +import std/[math, random, sequtils, strutils, times] + +proc main() = + randomize() + # Pick a number in 0..100. + let limit = 10^9 + let limbs = 10000 + let randomBigInt = toSeq(1..limbs).mapIt(rand(limit)).join("").initBigInt + let randomBigInt2 = toSeq(1..limbs).mapIt(rand(limit)).join("").initBigInt + let randomBigInt3 = toSeq(1..limbs).mapIt(rand(limit)).join("").initBigInt + + var + n = 30 + totalTime = 0.0 + minTime = -1.0 + maxTime = 0.0 + + for i in 1..n: + if i mod (n div 10) == 0: + echo "run: ", i + let startTime = cpuTime() + # Compute subproducts + let prod1 = randomBigInt * randomBigInt2 + let prod1bis = randomBigInt2 * randomBigInt + + # Check commutativity of the product + doAssert prod1 == prod1bis + + let prod2 = randomBigInt2 * randomBigInt3 + let prod3 = randomBigInt * randomBigInt3 + let product = prod2 * randomBigInt + + # Check associativity of the product + doAssert prod1 * randomBigInt3 == product + doAssert prod3 * randomBigInt2 == product + let endTime = cpuTime() + let time = endTime - startTime + totalTime += time + if minTime < 0 or time < minTime: minTime = time + if time > maxTime: maxTime = time + echo (avg: totalTime / n.float, min: minTime, max: maxTime) + +main() diff --git a/tests/tbigints.nim b/tests/tbigints.nim index 379bc36..240b267 100644 --- a/tests/tbigints.nim +++ b/tests/tbigints.nim @@ -223,7 +223,12 @@ proc main() = doAssert b == zero block: # multiplication - block: + block: # test sign + let one = 1.initBigInt + let negOne = -1.initBigInt + doAssert one * negOne == negOne + + block: # factors with 4 limbs let a = "1780983279228119273110576463639172624".initBigInt b = "1843917749452418885995463656480858321".initBigInt @@ -232,6 +237,11 @@ proc main() = doAssert -a * b == -c doAssert a * -b == -c doAssert -a * -b == c + # commutative: + doAssert b * a == c + doAssert -b * a == -c + doAssert b * -a == -c + doAssert -b * -a == c block: # self-multiplication var a = 12.initBigInt @@ -252,6 +262,51 @@ proc main() = a *= b doAssert a == zero + block: # large multiplication + let + a = "6277101735386680763835789123314955362437298222279840143829".initBigInt + b = "1461501637330902918203684832716283019655932313743".initBigInt + c = "9173994463960286046443283142914659534651409302161170887881670097401563075560864837488946597716929873341947".initBigInt + doAssert a * b == c + doAssert -a * b == -c + doAssert a * -b == -c + doAssert -a * -b == c + # commutative: + doAssert b * a == c + doAssert -b * a == -c + doAssert b * -a == -c + doAssert -b * -a == c + + block: # big by small multiplication + let + a = "9173994463960286046443283142914659534651409302161170887881670097401563075560864837488946597716929873341947".initBigInt + b = "155".initBigInt + c = "1421969141913844337198708887151772227870968441834981487621658865097242276711934049810786722646124130368001785".initBigInt + doAssert a * b == c + doAssert -a * b == -c + doAssert a * -b == -c + doAssert -a * -b == c + # commutative: + doAssert b * a == c + doAssert -b * a == -c + doAssert b * -a == -c + doAssert -b * -a == c + + block: # factors with 17 limbs + let c = "15456863493948186026689401110531937466657435954521677287549013772194751214595085262021623597960658907994197330891108031896474775438991400654520526954653285".initBigInt + let d = "20867311096234429137120990056519061484140179793024844459539745043528236531589522382271230666075358518275274769618792229717222657110424037636116966396665200".initBigInt + var r: BigInt = 0.initBigInt + karatsubaMultiplication(r, c, d) + doAssert c * d == "322543179100245850295291096700090285623536165554432133161470913224665233565153206743023505404409261647296075477738317301701554637184306640109864382144645081119052516436652162825894456855767719709860985552674755702938369565636714472650667032224717209489767579823588160939485446085000195032327964706246225182000".initBigInt + doAssert c * d == r + doAssert d * c == r + + block: # factors with 65 limbs + let e = initBigInt("1f3b839241b0aacc183858dc7a75a773e7bad642a9f426ef499d91e09c9f99a88ec9a14d5ee51175faeaa10d2fb06f3ee37d2f50fe755c2c963aeb539cd55c0e14f5a23f04c64839c22bd4108034b7afc95e01a1c2fe605d8b1930926e886a8f3d7fc09acd54d388cb5d4b3a3fb4eaf6781173ab3a0cd8ad3119c37dd2cf05544235d7b85b2c96d2ed29e1a685820c4afdd824bd8878f1b6a3f52a57eb886efaa737af47161c89f298d908aa950979b8c2615d4e03b47ee87a5381ca39d9ec4788d7abd07b174913b962c02cdd5f8319722a3345eb38d3ebdd51dec66a58e89902151539298c41446758bac66923c910fd7a2d12d0d5c8bb688970b8a77e7d5fc", base = 16) + let f = initBigInt("116be4e445ea68066fca5652e472eb1c1a5fb850311126a8a91fd6f1199f92a9d6602a81bb5e500d163b01df7eec15e41109c62f6c83425027272823d9888a51c93422d47ba4e1cecb94d6fa02eb27df537038b2ac7c9c264634b8febc452c5c9043ddc7d2eddd04f1f743d85cfefd0864441bb9cbf46308138d037b2057980c8b6d215e6bd2c0a73d64c176b7b59452a2c7968a121e5d46c859d85678acd0d4927509418fd351331791fdb7ad041ee2e7d5975867f1812d1e17a41f5a7c0735ebe224294f5ca9d607e95a8722adf58f676b23da1563ac62a52352c10efd0bf5cc8b5eb9ddd7fd1c22bc307d5edf86474f66302fd7cd288a9bc251d3ba45a851b", base = 16) + doAssert e * f == initBigInt("2201d898f354480704067f8513e7b8b365db60c9132d96e54b8546c05c59a08f50b8d8488841d9e893b3c6de34e22b70f2f6c9bc682700c060b60b6e614c9cc39a2c9a9c13ccc412a512c8126f60e1572d20281855e63d019be43a34a929c20818d05527e75f9edf4c5c9a096c4412f879ca14dd3f8bc48aafea6ce17223adefc1e55ca8216ed3d6d351f08dc38a5e6fed7cb3abd1844bfaed632a1571d0017f1285b2e7762c8c3ad0adc781f47df619f462415b8b4e3496fea3d1b1f4443184a5f5fdc155af2f62d861c9ab321a5083c07b4fdfc384aaa6c4a09559e1d7383b5a3fd9f6c9dfe2079c13bfcf307f98de6e16e474b55e94dcf9dbaf3e90ba38f37ddc69c318ed680a0db8a5d5257a1214c765b267f3df8352ad5b4ee9e9c27ca7085ee5687e6afc8108b683c622613c003c068b60bed656d2dc6a32b1f7b079194108301da8d8049f5b64e88da091803bf1582a45fd24f242f2d9b9d8090c4d088ea31faa3e997d20688481b8f1847524f28153e0ba5c3017338cf470c906d8b27352082741dcfb81ec81a3268569424f791c9a82777d66f2b6a52e0653843057da444b55ddb5f517f1676daafa3413ee3dc6dc0a8edc7cfcbd4b6dd5653957baa93e35fad6908addc018706b0acf64cd5cfe3ee462b57e2cc58f641a883a693505fab131a8f51f22cc34ff694af1e4ca0b4d8469067a76b378783b190c2377d94", base = 16) + doAssert f * e == e * f + block: # shift let x = "190485713846014693847".initBigInt