Skip to content

Commit 9f041d0

Browse files
committed
Optimize division for values that can fit a single word
1 parent 53dba72 commit 9f041d0

File tree

2 files changed

+95
-59
lines changed

2 files changed

+95
-59
lines changed

bint.lua

Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -394,18 +394,18 @@ do
394394
end
395395
end
396396

397-
-- Get the quotient and remainder for base digits
398-
local function xremainder(dividend, divisor)
399-
local quo = bint_newempty()
400-
local rem
397+
-- Single word division modulus
398+
local function sudivmod(nume, deno)
399+
local quot = bint_newempty()
400+
local rema
401401
local carry = 0
402402
for i=BINT_SIZE,1,-1 do
403-
carry = carry | dividend[i]
404-
quo[i] = carry // divisor
405-
rem = carry % divisor
406-
carry = rem << BINT_WORDBITS
403+
carry = carry | nume[i]
404+
quot[i] = carry // deno
405+
rema = carry % deno
406+
carry = rema << BINT_WORDBITS
407407
end
408-
return quo, rem
408+
return quot, rema
409409
end
410410

411411
--- Convert a bint to a string in the desired base.
@@ -462,7 +462,7 @@ function bint.tobase(x, base, unsigned)
462462
-- spit out base digits
463463
while not stop do
464464
local xd
465-
x, xd = xremainder(x, basepow)
465+
x, xd = sudivmod(x, basepow)
466466
stop = x:iszero()
467467
for _=1,step do
468468
local d
@@ -1073,67 +1073,87 @@ end
10731073
-- @see bint.udiv
10741074
-- @see bint.umod
10751075
function bint.udivmod(x, y)
1076-
local dividend = bint.new(x)
1077-
local divisor = bint_assert_convert(y)
1078-
local quo = bint.zero()
1079-
assert(not divisor:iszero(), 'attempt to divide by zero')
1080-
if divisor:isone() then
1081-
return dividend, bint.zero()
1082-
elseif dividend:ult(divisor) then
1083-
return quo, dividend
1084-
end
1085-
-- align leftmost digits in dividend and divisor
1086-
local divisorlbit = findleftbit(divisor)
1087-
local divdendlbit, divdendsize = findleftbit(dividend)
1088-
local bit = divdendlbit - divisorlbit
1089-
divisor = divisor << bit
1076+
local nume = bint.new(x)
1077+
local deno = bint_assert_convert(y)
1078+
-- compute if high bits of denominator are all zeros
1079+
local ishighzero = true
1080+
for i=BINT_SIZE,2,-1 do
1081+
if deno[i] ~= 0 then
1082+
ishighzero = false
1083+
break
1084+
end
1085+
end
1086+
if ishighzero then
1087+
-- try to divide by a single word (optimization)
1088+
local low = deno[1]
1089+
assert(low ~= 0, 'attempt to divide by zero')
1090+
if low == 1 then
1091+
-- denominator is one
1092+
return nume, bint.zero()
1093+
elseif low <= (BINT_WORDMSB - 1) then
1094+
-- can do single word division
1095+
local rema
1096+
nume, rema = sudivmod(nume, low)
1097+
return nume, bint.new(rema)
1098+
end
1099+
end
1100+
if nume:ult(deno) then
1101+
-- denominator is greater than denominator
1102+
return bint.zero(), nume
1103+
end
1104+
-- align leftmost digits in numerator and denominator
1105+
local denolbit = findleftbit(deno)
1106+
local numelbit, numesize = findleftbit(nume)
1107+
local bit = numelbit - denolbit
1108+
deno = deno << bit
10901109
local wordmaxp1 = BINT_WORDMAX + 1
10911110
local wordbitsm1 = BINT_WORDBITS - 1
1092-
local divisorsize = divdendsize
1111+
local denosize = numesize
1112+
local quot = bint.zero()
10931113
while bit >= 0 do
1094-
-- compute divisor <= dividend
1114+
-- compute denominator <= numerator
10951115
local le = true
1096-
local size = math.max(divdendsize, divisorsize)
1116+
local size = math.max(numesize, denosize)
10971117
for i=size,1,-1 do
1098-
local a, b = divisor[i], dividend[i]
1118+
local a, b = deno[i], nume[i]
10991119
if a ~= b then
11001120
le = a < b
11011121
break
11021122
end
11031123
end
1104-
-- if the portion of the dividend above the divisor is greater or equal than to the divisor
1124+
-- if the portion of the numerator above the denominator is greater or equal than to the denominator
11051125
if le then
1106-
-- subtract divisor from the portion of the dividend
1126+
-- subtract denominator from the portion of the numerator
11071127
local borrow = 0
11081128
for i=1,size do
1109-
local res = (dividend[i] + wordmaxp1) - (divisor[i] + borrow)
1110-
dividend[i] = res & BINT_WORDMAX
1129+
local res = (nume[i] + wordmaxp1) - (deno[i] + borrow)
1130+
nume[i] = res & BINT_WORDMAX
11111131
borrow = res <= BINT_WORDMAX and 1 or 0
11121132
end
11131133
-- concatenate 1 to the right bit of the quotient
11141134
local i = (bit // BINT_WORDBITS) + 1
1115-
quo[i] = quo[i] | (1 << (bit % BINT_WORDBITS))
1135+
quot[i] = quot[i] | (1 << (bit % BINT_WORDBITS))
11161136
end
1117-
-- shift right the divisor in one bit
1118-
for i=1,divisorsize-1 do
1119-
divisor[i] = ((divisor[i] >> 1) | (divisor[i+1] << wordbitsm1)) & BINT_WORDMAX
1137+
-- shift right the denominator in one bit
1138+
for i=1,denosize-1 do
1139+
deno[i] = ((deno[i] >> 1) | (deno[i+1] << wordbitsm1)) & BINT_WORDMAX
11201140
end
1121-
local lastdivisorword = divisor[divisorsize] >> 1
1122-
divisor[divisorsize] = lastdivisorword
1123-
-- recalculate divisor size (optimization)
1124-
if lastdivisorword == 0 then
1125-
while divisor[divisorsize] == 0 do
1126-
divisorsize = divisorsize - 1
1141+
local lastdenoword = deno[denosize] >> 1
1142+
deno[denosize] = lastdenoword
1143+
-- recalculate denominator size (optimization)
1144+
if lastdenoword == 0 then
1145+
while deno[denosize] == 0 do
1146+
denosize = denosize - 1
11271147
end
1128-
if divisorsize == 0 then
1148+
if denosize == 0 then
11291149
break
11301150
end
11311151
end
11321152
-- decrement current set bit for the quotient
11331153
bit = bit - 1
11341154
end
1135-
-- the remaining dividend is the remainder
1136-
return quo, dividend
1155+
-- the remaining numerator is the remainder
1156+
return quot, nume
11371157
end
11381158

11391159
--- Perform unsigned division between two integers considering bints.
@@ -1153,8 +1173,8 @@ end
11531173
-- @raise Asserts on attempt to divide by zero
11541174
-- or if the inputs are not convertible to integers.
11551175
function bint.umod(x, y)
1156-
local _, rem = bint.udivmod(x, y)
1157-
return rem
1176+
local _, rema = bint.udivmod(x, y)
1177+
return rema
11581178
end
11591179

11601180
--- Perform integer floor division and modulo operation between two numbers considering bints.
@@ -1171,25 +1191,25 @@ function bint.idivmod(x, y)
11711191
if iy:isminusone() then
11721192
return -ix, bint.zero()
11731193
end
1174-
local quo, rem = bint.udivmod(ix:abs(), iy:abs())
1194+
local quot, rema = bint.udivmod(ix:abs(), iy:abs())
11751195
local isnumneg, isdenomneg = ix:isneg(), iy:isneg()
11761196
if isnumneg ~= isdenomneg then
1177-
quo:_unm()
1197+
quot:_unm()
11781198
-- round quotient towards minus infinity
1179-
if not rem:iszero() then
1180-
quo:_dec()
1199+
if not rema:iszero() then
1200+
quot:_dec()
11811201
-- adjust the remainder
11821202
if isnumneg and not isdenomneg then
1183-
rem:_unm():_add(y)
1203+
rema:_unm():_add(y)
11841204
elseif isdenomneg and not isnumneg then
1185-
rem:_add(y)
1205+
rema:_add(y)
11861206
end
11871207
end
11881208
elseif isnumneg then
11891209
-- adjust the remainder
1190-
rem:_unm()
1210+
rema:_unm()
11911211
end
1192-
return quo, rem
1212+
return quot, rema
11931213
else
11941214
local nx, ny = bint.tonumber(x), bint.tonumber(y)
11951215
return nx // ny, nx % ny
@@ -1204,7 +1224,23 @@ end
12041224
-- @return The quotient, a bint or lua number.
12051225
-- @raise Asserts on attempt to divide by zero.
12061226
function bint.__idiv(x, y)
1207-
return (bint.idivmod(x, y))
1227+
local ix, iy = bint.tobint(x), bint.tobint(y)
1228+
if ix and iy then
1229+
if iy:isminusone() then
1230+
return -ix, bint.zero()
1231+
end
1232+
local quot, rema = bint.udivmod(ix:abs(), iy:abs())
1233+
if ix:isneg() ~= iy:isneg() then
1234+
quot:_unm()
1235+
-- round quotient towards minus infinity
1236+
if not rema:iszero() then
1237+
quot:_dec()
1238+
end
1239+
end
1240+
return quot, rema
1241+
else
1242+
return bint.tonumber(x) // bint.tonumber(y)
1243+
end
12081244
end
12091245

12101246
--- Perform division between two numbers considering bints.
@@ -1224,8 +1260,8 @@ end
12241260
-- @return The remainder, a bint or lua number.
12251261
-- @raise Asserts on attempt to divide by zero.
12261262
function bint.__mod(x, y)
1227-
local _, rem = bint.idivmod(x, y)
1228-
return rem
1263+
local _, rema = bint.idivmod(x, y)
1264+
return rema
12291265
end
12301266

12311267
--- Perform integer power between two integers considering bints.

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2368,7 +2368,7 @@ <h2 class="section-header "><a name="Fields"></a>Fields</h2>
23682368
</div> <!-- id="main" -->
23692369
<div id="about">
23702370
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
2371-
<i style="float:right;">Last updated 2020-07-10 22:49:06 </i>
2371+
<i style="float:right;">Last updated 2020-07-11 10:34:28 </i>
23722372
</div> <!-- id="about" -->
23732373
</div> <!-- id="container" -->
23742374
</body>

0 commit comments

Comments
 (0)