# Stint # Copyright 2018-2023 Status Research & Development GmbH # Licensed under either of # # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # # at your option. This file may not be copied, modified, or distributed except according to those terms. import # Status lib stew/bitops2, # Internal ./datatypes, ./uint_bitwise, ./primitives/[addcarry_subborrow, extended_precision] # Division # -------------------------------------------------------- func shortDiv*(a: var Limbs, k: Word): Word = ## Divide `a` by k in-place and return the remainder result = Word(0) let clz = leadingZeros(k) let normK = k shl clz for i in countdown(a.len-1, 0): # dividend = 2^64 * remainder + a[i] var hi = result var lo = a[i] if hi == 0: if lo < k: a[i] = 0 elif lo == k: a[i] = 1 result = 0 continue # Normalize, shifting the remainder by clz(k) cannot overflow. hi = (hi shl clz) or (lo shr (WordBitWidth - clz)) lo = lo shl clz div2n1n(a[i], result, hi, lo, normK) # Undo normalization result = result shr clz func shlAddMod_multi(a: var openArray[Word], c: Word, M: openArray[Word], mBits: int): Word = ## Fused modular left-shift + add ## Shift input `a` by a word and add `c` modulo `M` ## ## Specialized for M being a multi-precision integer. ## ## With a word W = 2^WordBitWidth and a modulus M ## Does a <- a * W + c (mod M) ## and returns q = (a * W + c ) / M ## ## The modulus `M` most-significant bit at `mBits` MUST be set. # Assuming 64-bit words let hi = a[^1] # Save the high word to detect carries let R = mBits and (WordBitWidth - 1) # R = mBits mod 64 var a0, a1, m0: Word if R == 0: # If the number of mBits is a multiple of 64 a0 = a[^1] # copyWords(a, 1, a, 0, a.len-1) # we can just shift words a[0] = c # and replace the first one by c a1 = a[^1] m0 = M[^1] else: # Else: need to deal with partial word shifts at the edge. let clz = WordBitWidth-R a0 = (a[^1] shl clz) or (a[^2] shr R) copyWords(a, 1, a, 0, a.len-1) a[0] = c a1 = (a[^1] shl clz) or (a[^2] shr R) m0 = (M[^1] shl clz) or (M[^2] shr R) # m0 has its high bit set. (a0, a1)/m0 fits in a limb. # Get a quotient q, at most we will be 2 iterations off # from the true quotient var q: Word # Estimate quotient if a0 == m0: # if a_hi == divisor q = high(Word) # quotient = MaxWord (0b1111...1111) elif a0 == 0 and a1 < m0: # elif q == 0, true quotient = 0 q = 0 return q else: var r: Word div2n1n(q, r, a0, a1, m0) # else instead of being of by 0, 1 or 2 q -= 1 # we return q-1 to be off by -1, 0 or 1 # Now substract a*2^64 - q*m var carry = Word(0) var overM = true # Track if quotient greater than the modulus for i in 0 ..< M.len: var qm_lo: Word block: # q*m # q * p + carry (doubleword) carry from previous limb muladd1(carry, qm_lo, q, M[i], carry) block: # a*2^64 - q*m var borrow: Borrow subB(borrow, a[i], a[i], qm_lo, Borrow(0)) carry += Word(borrow) # Adjust if borrow if a[i] != M[i]: overM = a[i] > M[i] # Fix quotient, the true quotient is either q-1, q or q+1 # # if carry < q or carry == q and overM we must do "a -= M" # if carry > hi (negative result) we must do "a += M" if carry > hi: var c = Carry(0) for i in 0 ..< a.len: addC(c, a[i], a[i], M[i], c) q -= 1 elif overM or (carry < hi): var b = Borrow(0) for i in 0 ..< a.len: subB(b, a[i], a[i], M[i], b) q += 1 return q func shlAddMod(a: var openArray[Word], c: Word, M: openArray[Word], mBits: int): Word {.inline.}= ## Fused modular left-shift + add ## Shift input `a` by a word and add `c` modulo `M` ## ## With a word W = 2^WordBitWidth and a modulus M ## Does a <- a * W + c (mod M) ## and returns q = (a * W + c ) / M ## ## The modulus `M` most-significant bit at `mBits` MUST be set. if mBits <= WordBitWidth: # If M fits in a single limb # We normalize M with clz so that the MSB is set # And normalize (a * 2^64 + c) by R as well to maintain the result # This ensures that (a0, a1)/p0 fits in a limb. let R = mBits and (WordBitWidth - 1) # (hi, lo) = a * 2^64 + c if R == 0: # We can delegate this R == 0 case to the # shlAddMod_multi, with the same result. # But isn't it faster to handle it here? var q, r: Word div2n1n(q, r, a[0], c, M[0]) a[0] = r return q else: let clz = WordBitWidth-R let hi = (a[0] shl clz) or (c shr R) let lo = c shl clz let m0 = M[0] shl clz var q, r: Word div2n1n(q, r, hi, lo, m0) a[0] = r shr clz return q else: return shlAddMod_multi(a, c, M, mBits) func divRem*( q, r: var openArray[Word], a, b: openArray[Word] ) = let (aBits, aLen) = usedBitsAndWords(a) let (bBits, bLen) = usedBitsAndWords(b) let rLen = bLen if unlikely(bBits == 0): raise newException(DivByZeroDefect, "You attempted to divide by zero") if aBits < bBits: # if a uses less bits than b, # a < b, so q = 0 and r = a copyWords(r, 0, a, 0, aLen) for i in aLen ..< r.len: # r.len >= rLen r[i] = 0 for i in 0 ..< q.len: q[i] = 0 else: # The length of a is at least the divisor # We can copy bLen-1 words # and modular shift-lef-add the rest let aOffset = aLen - bLen copyWords(r, 0, a, aOffset+1, bLen-1) r[rLen-1] = 0 # Now shift-left the copied words while adding the new word mod b when nimvm: # workaround nim bug #22095 var rr = @(r.toOpenArray(0, rLen-1)) var bb = @(b.toOpenArray(0, bLen-1)) for i in countdown(aOffset, 0): q[i] = shlAddMod( rr, a[i], bb, bBits ) for i in 0..rLen-1: r[i] = rr[i] else: for i in countdown(aOffset, 0): q[i] = shlAddMod( r.toOpenArray(0, rLen-1), a[i], b.toOpenArray(0, bLen-1), bBits ) # Clean up extra words for i in aOffset+1 ..< q.len: q[i] = 0 for i in rLen ..< r.len: r[i] = 0 # ###################################################################### # Division implementations # # Multi-precision division is a costly # and also difficult to implement operation # ##### Research ##### # Overview of division algorithms: # - https://gmplib.org/manual/Division-Algorithms.html#Division-Algorithms # - https://gmplib.org/~tege/division-paper.pdf # - Comparison of fast division algorithms for large integers: http://bioinfo.ict.ac.cn/~dbu/AlgorithmCourses/Lectures/Lec5-Fast-Division-Hasselstrom2003.pdf # Schoolbook / Knuth Division (Algorithm D) # - https://skanthak.homepage.t-online.de/division.html # Review of implementation flaws # - Hacker's Delight https://github.com/hcs0/Hackers-Delight/blob/master/divmnu64.c.txt # - LLVM: https://github.com/llvm-mirror/llvm/blob/2c4ca68/lib/Support/APInt.cpp#L1289-L1451 # - ctbignum: https://github.com/niekbouman/ctbignum/blob/v0.5/include/ctbignum/division.hpp # - Modern Computer Arithmetic - https://members.loria.fr/PZimmermann/mca/mca-cup-0.5.9.pdf # p14 - 1.4.1 Naive Division # - Handbook of Applied Cryptography - https://cacr.uwaterloo.ca/hac/about/chap14.pdf # Chapter 14 algorithm 14.2.5 # Smith Method (and derivatives) # This method improves Knuth algorithm by ~3x by removing regular normalization # - A Multiple-Precision Division Algorithm, David M Smith # American mathematical Society, 1996 # https://www.ams.org/journals/mcom/1996-65-213/S0025-5718-96-00688-6/S0025-5718-96-00688-6.pdf # # - An Efficient Multiple-Precision Division Algorithm, # Liusheng Huang, Hong Zhong, Hong Shen, Yonglong Luo, 2005 # https://ieeexplore.ieee.org/document/1579076 # # - Efficient multiple-precision integer division algorithm # Debapriyay Mukhopadhyaya, Subhas C.Nandy, 2014 # https://www.sciencedirect.com/science/article/abs/pii/S0020019013002627 # Recursive division by Burnikel and Ziegler (http://www.mpi-sb.mpg.de/~ziegler/TechRep.ps.gz): # - Python implementation: https://bugs.python.org/file11060/fast_div.py and discussion https://bugs.python.org/issue3451 # - C++ implementation: https://github.com/linbox-team/givaro/blob/master/src/kernel/recint/rudiv.h # - The Handbook of Elliptic and Hyperelliptic Cryptography Algorithm 10.35 on page 188 has a more explicit version of the div2NxN algorithm. This algorithm is directly recursive and avoids the mutual recursion of the original paper's calls between div2NxN and div3Nx2N. # - Modern Computer Arithmetic - https://members.loria.fr/PZimmermann/mca/mca-cup-0.5.9.pdf # p18 - 1.4.3 Divide and Conquer Division # Newton Raphson Iterations # - Putty (constant-time): https://github.com/github/putty/blob/0.74/mpint.c#L1818-L2112 # - Modern Computer Arithmetic - https://members.loria.fr/PZimmermann/mca/mca-cup-0.5.9.pdf # p18 - 1.4.3 Divide and Conquer Division # Other libraries that can be used as reference for alternative (?) implementations: # - TTMath: https://github.com/status-im/nim-ttmath/blob/8f6ff2e57b65a350479c4012a53699e262b19975/src/headers/ttmathuint.h#L1530-L2383 # - LibTomMath: https://github.com/libtom/libtommath # - Google Abseil for uint128: https://github.com/abseil/abseil-cpp/tree/master/absl/numeric # Note: GMP/MPFR are GPL. The papers can be used but not their code. # Related research # - Efficient divide-and-conquer multiprecision integer division # William Hart, IEEE 2015 # https://github.com/wbhart/bsdnt # https://ieeexplore.ieee.org/document/7203801