nim-stint/stint/private/uint_div.nim

305 lines
11 KiB
Nim

# 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,
./uint_shift,
./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]
# 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 binaryShiftDiv[qLen, rLen, uLen, vLen: static int](
# q: var Limbs[qLen],
# r: var Limbs[rLen],
# u: Limbs[uLen],
# v: Limbs[vLen]) =
# ## Division for multi-precision unsigned uint
# ## Implementation through binary shift division
# doAssert y.isZero.not() # This should be checked on release mode in the divmod caller proc
# type SubTy = type x.lo
# var
# shift = y.leadingZeros - x.leadingZeros
# d = y shl shift
# r = x
# while shift >= 0:
# q += q
# if r >= d:
# r -= d
# q.lo = q.lo or one(SubTy)
# d = d shr 1
# dec(shift)
func knuthDivLE(
q: var StUint,
r: var StUint,
u: StUint,
v: StUint,
needRemainder: bool) =
## Compute the quotient and remainder (if needed)
## of the division of u by v
##
## - q must be of size uLen - vLen + 1 (assuming u and v uses all words)
## - r must be of size vLen (assuming v uses all words)
## - uLen >= vLen
##
## For now only LittleEndian is implemented
#
# Resources at the bottom of the file
const
qLen = q.limbs.len
rLen = r.limbs.len
uLen = u.limbs.len
vLen = v.limbs.len
template `[]`(a: Stuint, i: int): Word = a.limbs[i]
template `[]=`(a: Stuint, i: int, val: Word) = a.limbs[i] = val
# Find the most significant word with actual set bits
# and get the leading zero count there
var divisorLen = vLen
var clz: int
for w in mostToLeastSig(v):
if w != 0:
clz = leadingZeros(w)
break
else:
divisorLen -= 1
doAssert divisorLen != 0, "Division by zero. Abandon ship!"
# Divisor is a single word.
if divisorLen == 1:
q.copyFrom(u)
r.leastSignificantWord() = q.limbs.shortDiv(v.leastSignificantWord())
# zero all but the least significant word
var lsw = true
for w in leastToMostSig(r):
if lsw:
lsw = false
else:
w = 0
return
var un {.noInit.}: Limbs[uLen+1]
var vn {.noInit.}: Limbs[vLen] # [mswLen .. vLen] range is unused
# Normalize so that the divisor MSB is set,
# vn cannot overflow, un can overflowed by 1 word at most, hence uLen+1
un.shlSmallOverflowing(u.limbs, clz)
vn.shlSmall(v.limbs, clz)
static: doAssert cpuEndian == littleEndian, "Currently the division algorithm requires little endian ordering of the limbs"
# TODO: is it worth it to have the uint be the exact same extended precision representation
# as a wide int (say uint128 or uint256)?
# in big-endian, the following loop must go the other way and the -1 must be +1
let vhi = vn[divisorLen-1]
let vlo = vn[divisorLen-2]
for j in countdown(uLen - divisorLen, 0, 1):
# Compute qhat estimate of q[j] (off by 0, 1 and rarely 2)
var qhat, rhat: Word
let uhi = un[j+divisorLen]
let ulo = un[j+divisorLen-1]
div2n1n(qhat, rhat, uhi, ulo, vhi)
var mhi, mlo: Word
var rhi, rlo: Word
mul(mhi, mlo, qhat, vlo)
rhi = rhat
rlo = ulo
# if r < m, adjust approximation, up to twice
while rhi < mhi or (rhi == mhi and rlo < mlo):
qhat -= 1
rhi += vhi
# Found the quotient
q[j] = qhat
# un -= qhat * v
var borrow = Borrow(0)
var qvhi, qvlo: Word
for i in 0 ..< divisorLen-1:
mul(qvhi, qvlo, qhat, v[i])
subB(borrow, un[j+i], un[j+i], qvlo, borrow)
subB(borrow, un[j+i+1], un[j+i+1], qvhi, borrow)
# Last step
mul(qvhi, qvlo, qhat, v[divisorLen-1])
subB(borrow, un[j+divisorLen-1], un[j+divisorLen-1], qvlo, borrow)
qvhi += Word(borrow)
let isNeg = un[j+divisorLen] < qvhi
un[j+divisorLen] -= qvhi
if isNeg:
# oops, too big by one, add back
q[j] -= 1
var carry = Carry(0)
for i in 0 ..< divisorLen:
addC(carry, un[j+i], un[j+i], v[i], carry)
# Quotient is found, if remainder is needed we need to un-normalize un
if needRemainder:
# r.limbs.shrSmall(un, clz) - TODO
when cpuEndian == littleEndian:
# rLen+1 == un.len
for i in 0 ..< rLen:
r[i] = (un[i] shr clz) or (un[i+1] shl (WordBitWidth - clz))
else:
{.error: "Not Implemented for bigEndian".}
const BinaryShiftThreshold = 8 # If the difference in bit-length is below 8
# binary shift is probably faster
func divmod(q, r: var Stuint,
<<<<<<< HEAD
x: Limbs[xLen], y: Limbs[yLen], needRemainder: bool) =
=======
x, y: Stuint, needRemainder: bool) =
>>>>>>> 88858a7 (uint division - compile and pass the single limb tests)
let x_clz = x.leadingZeros()
let y_clz = y.leadingZeros()
# We short-circuit division depending on special-cases.
<<<<<<< HEAD
if unlikely(y.isZero):
raise newException(DivByZeroDefect, "You attempted to divide by zero")
elif y_clz == (bitsof(y) - 1):
=======
if unlikely(y.isZero()):
raise newException(DivByZeroError, "You attempted to divide by zero")
elif y_clz == (y.bits - 1):
>>>>>>> 88858a7 (uint division - compile and pass the single limb tests)
# y is one
q = x
# elif (x.hi or y.hi).isZero:
# # If computing just on the low part is enough
# (result.quot.lo, result.rem.lo) = divmod(x.lo, y.lo, needRemainder)
# elif (y and (y - one(type y))).isZero:
# # y is a power of 2. (this also matches 0 but it was eliminated earlier)
# # TODO. Would it be faster to use countTrailingZero (ctz) + clz == size(y) - 1?
# # Especially because we shift by ctz after.
# let y_ctz = bitsof(y) - y_clz - 1
# result.quot = x shr y_ctz
# if needRemainder:
# result.rem = x and (y - one(type y))
elif x == y:
q.setOne()
elif x < y:
r = x
# elif (y_clz - x_clz) < BinaryShiftThreshold:
# binaryShiftDiv(x, y, result.quot, result.rem)
else:
knuthDivLE(q, r, x, y, needRemainder)
func `div`*(x, y: Stuint): Stuint {.inline.} =
## Division operation for multi-precision unsigned uint
var tmp{.noInit.}: Stuint
divmod(result, tmp, x, y, needRemainder = false)
func `mod`*(x, y: Stuint): Stuint {.inline.} =
## Remainder operation for multi-precision unsigned uint
var tmp{.noInit.}: Stuint
divmod(tmp, result, x, y, needRemainder = true)
func divmod*(x, y: Stuint): tuple[quot, rem: Stuint] =
## Division and remainder operations for multi-precision unsigned uint
divmod(result.quot, result.rem, x, y, needRemainder = true)
# ######################################################################
# 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