Skip to content

Commit 1820a55

Browse files
authored
math.big: replace division with Knuth, improve performance (#25242)
1 parent 4dcf79e commit 1820a55

File tree

5 files changed

+133
-236
lines changed

5 files changed

+133
-236
lines changed

vlib/math/big/array_ops.v

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,17 @@ fn divide_digit_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut
181181
cmp_result := compare_digit_array(operand_a, operand_b)
182182
// a == b => q, r = 1, 0
183183
if cmp_result == 0 {
184-
quotient << 1
185-
for quotient.len > 1 {
186-
quotient.delete_last()
187-
}
184+
quotient[0] = 1
188185
remainder.clear()
189186
return
190187
}
191188

192189
// a < b => q, r = 0, a
193190
if cmp_result < 0 {
194191
quotient.clear()
195-
remainder << operand_a
192+
for i in 0 .. operand_a.len {
193+
remainder[i] = operand_a[i]
194+
}
196195
return
197196
}
198197
if operand_b.len == 1 {
@@ -210,38 +209,36 @@ fn divide_array_by_digit(operand_a []u64, divisor u64, mut quotient []u64, mut r
210209
// 1 digit for both dividend and divisor
211210
dividend := operand_a[0]
212211
q := dividend / divisor
213-
if q != 0 {
214-
quotient << q
215-
}
212+
quotient[0] = q
213+
216214
rem := dividend % divisor
217-
if rem != 0 {
218-
remainder << rem
219-
}
215+
remainder[0] = rem
216+
217+
shrink_tail_zeros(mut quotient)
218+
shrink_tail_zeros(mut remainder)
220219
return
221220
}
222221
// Dividend has more digits
223222
mut rem := u64(0)
224223
mut quo := u64(0)
225-
mut qtemp := []u64{len: quotient.cap}
226-
divisor64 := u64(divisor)
227224

228225
// Perform division step by step
229226
for index := operand_a.len - 1; index >= 0; index-- {
230227
hi := rem >> (64 - digit_bits)
231228
lo := rem << digit_bits | operand_a[index]
232-
quo, rem = bits.div_64(hi, lo, divisor64)
233-
qtemp[index] = quo & max_digit
229+
quo, rem = bits.div_64(hi, lo, divisor)
230+
quotient[index] = quo & max_digit
234231
}
232+
235233
// Remove leading zeros from quotient
236-
shrink_tail_zeros(mut qtemp)
237-
quotient << qtemp
238-
remainder << rem
234+
shrink_tail_zeros(mut quotient)
235+
remainder[0] = rem
239236
shrink_tail_zeros(mut remainder)
240237
}
241238

242239
@[inline]
243240
fn divide_array_by_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut remainder []u64) {
244-
binary_divide_array_by_array(operand_a, operand_b, mut quotient, mut remainder)
241+
knuth_divide_array_by_array(operand_a, operand_b, mut quotient, mut remainder)
245242
}
246243

247244
// Shifts the contents of the original array by the given amount of bits to the left.

vlib/math/big/array_ops_test.v

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ fn test_compare_digit_array_02() {
136136
fn test_divide_digit_array_01() {
137137
a := [u64(14)]
138138
b := [u64(2)]
139-
mut q := []u64{cap: 1}
140-
mut r := []u64{cap: 1}
139+
mut q := []u64{len: 1}
140+
mut r := []u64{len: 1}
141141

142142
divide_digit_array(a, b, mut q, mut r)
143143
assert q == [u64(7)]
@@ -147,8 +147,8 @@ fn test_divide_digit_array_01() {
147147
fn test_divide_digit_array_02() {
148148
a := [u64(14)]
149149
b := [u64(15)]
150-
mut q := []u64{cap: 1}
151-
mut r := []u64{cap: 1}
150+
mut q := []u64{len: 1}
151+
mut r := []u64{len: 1}
152152

153153
divide_digit_array(a, b, mut q, mut r)
154154
assert q == []u64{len: 0}
@@ -158,8 +158,8 @@ fn test_divide_digit_array_02() {
158158
fn test_divide_digit_array_03() {
159159
a := [u64(0), 4]
160160
b := [u64(0), 1]
161-
mut q := []u64{cap: a.len - b.len + 1}
162-
mut r := []u64{cap: a.len}
161+
mut q := []u64{len: a.len - b.len + 1}
162+
mut r := []u64{len: a.len}
163163

164164
divide_digit_array(a, b, mut q, mut r)
165165
assert q == [u64(4)]
@@ -169,8 +169,8 @@ fn test_divide_digit_array_03() {
169169
fn test_divide_digit_array_04() {
170170
a := [u64(2), 4]
171171
b := [u64(0), 1]
172-
mut q := []u64{cap: a.len - b.len + 1}
173-
mut r := []u64{cap: a.len}
172+
mut q := []u64{len: a.len - b.len + 1}
173+
mut r := []u64{len: a.len}
174174

175175
divide_digit_array(a, b, mut q, mut r)
176176
assert q == [u64(4)]
@@ -180,8 +180,8 @@ fn test_divide_digit_array_04() {
180180
fn test_divide_digit_array_05() {
181181
a := [u64(3)]
182182
b := [u64(2)]
183-
mut q := []u64{cap: a.len - b.len + 1}
184-
mut r := []u64{cap: a.len}
183+
mut q := []u64{len: a.len - b.len + 1}
184+
mut r := []u64{len: a.len}
185185

186186
divide_digit_array(a, b, mut q, mut r)
187187
assert q == [u64(1)]

vlib/math/big/division_array_ops.v

Lines changed: 97 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,6 @@ module big
22

33
import math.bits
44

5-
// suppose operand_a bigger than operand_b and both not null.
6-
// Both quotient and remaider are allocated but of length 0
7-
@[direct_array_access]
8-
fn binary_divide_array_by_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut remainder []u64) {
9-
remainder << operand_a
10-
11-
len_diff := operand_a.len - operand_b.len
12-
$if debug {
13-
assert len_diff >= 0
14-
}
15-
16-
// we must do in place shift and operations.
17-
mut divisor := []u64{cap: operand_b.len}
18-
for _ in 0 .. len_diff {
19-
divisor << u64(0)
20-
}
21-
divisor << operand_b
22-
for _ in 0 .. len_diff + 1 {
23-
quotient << u64(0)
24-
}
25-
26-
lead_zer_remainder := u32(bits.leading_zeros_64(remainder.last()) - (64 - digit_bits))
27-
lead_zer_divisor := u32(bits.leading_zeros_64(divisor.last()) - (64 - digit_bits))
28-
bit_offset := (u32(digit_bits) * u32(len_diff)) + (lead_zer_divisor - lead_zer_remainder)
29-
30-
// align
31-
if lead_zer_remainder < lead_zer_divisor {
32-
left_shift_in_place(mut divisor, lead_zer_divisor - lead_zer_remainder)
33-
} else if lead_zer_remainder > lead_zer_divisor {
34-
left_shift_in_place(mut remainder, lead_zer_remainder - lead_zer_divisor)
35-
}
36-
37-
$if debug {
38-
assert left_align_p(divisor[divisor.len - 1], remainder[remainder.len - 1])
39-
}
40-
for bit_idx := int(bit_offset); bit_idx >= 0; bit_idx-- {
41-
if greater_equal_from_end(remainder, divisor) {
42-
bit_set(mut quotient, bit_idx)
43-
subtract_align_last_byte_in_place(mut remainder, divisor)
44-
}
45-
right_shift_in_place(mut divisor, 1)
46-
}
47-
48-
// adjust
49-
if lead_zer_remainder > lead_zer_divisor {
50-
right_shift_in_place(mut remainder, lead_zer_remainder - lead_zer_divisor)
51-
}
52-
shrink_tail_zeros(mut remainder)
53-
shrink_tail_zeros(mut quotient)
54-
}
55-
565
// help routines for cleaner code but inline for performance
576
// quicker than BitField.set_bit
587
@[direct_array_access; inline]
@@ -65,80 +14,112 @@ fn bit_set(mut a []u64, n int) {
6514
a[byte_offset] |= mask
6615
}
6716

68-
// a.len is greater or equal to b.len
69-
// returns true if a >= b (completed with zeroes)
70-
@[direct_array_access; inline]
71-
fn greater_equal_from_end(a []u64, b []u64) bool {
72-
$if debug {
73-
assert a.len >= b.len
74-
}
75-
offset := a.len - b.len
76-
for index := a.len - 1; index >= offset; index-- {
77-
if a[index] > b[index - offset] {
78-
return true
79-
} else if a[index] < b[index - offset] {
80-
return false
81-
}
82-
}
83-
return true
84-
}
17+
@[direct_array_access]
18+
fn knuth_divide_array_by_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut remainder []u64) {
19+
m := operand_a.len - operand_b.len
20+
n := operand_b.len
21+
mut u := []u64{len: operand_a.len + 1}
22+
mut v := []u64{len: n}
23+
leading_zeros := bits.leading_zeros_64(operand_b.last()) - (64 - digit_bits)
8524

86-
// a := a - b supposed a >= b
87-
// attention the b operand is align with the a operand before the subtraction
88-
@[direct_array_access; inline]
89-
fn subtract_align_last_byte_in_place(mut a []u64, b []u64) {
90-
mut carry := u64(0)
91-
mut new_carry := u64(0)
92-
offset := a.len - b.len
93-
for index := a.len - b.len; index < a.len; index++ {
94-
if a[index] < (b[index - offset] + carry) || (b[index - offset] == max_digit && carry > 0) {
95-
new_carry = 1
96-
} else {
97-
new_carry = 0
25+
if leading_zeros > 0 {
26+
mut carry := u64(0)
27+
amount := digit_bits - leading_zeros
28+
for i in 0 .. operand_a.len {
29+
temp := (operand_a[i] << leading_zeros) | carry
30+
u[i] = temp & max_digit
31+
carry = operand_a[i] >> amount
32+
}
33+
u[operand_a.len] = carry
34+
carry = 0
35+
for i in 0 .. operand_b.len {
36+
temp := (operand_b[i] << leading_zeros) | carry
37+
v[i] = temp & max_digit
38+
carry = operand_b[i] >> amount
39+
}
40+
} else {
41+
for i in 0 .. operand_a.len {
42+
u[i] = operand_a[i]
43+
}
44+
for i in 0 .. operand_b.len {
45+
v[i] = operand_b[i]
9846
}
99-
a[index] -= (b[index - offset] + carry)
100-
a[index] = a[index] & max_digit
101-
carry = new_carry
10247
}
103-
$if debug {
104-
assert carry == 0
48+
49+
if remainder.len >= (n + 1) {
50+
remainder.trim(n + 1)
51+
} else {
52+
remainder = []u64{len: n + 1}
10553
}
106-
}
10754

108-
// logical left shift
109-
// there is no overflow. We know that the last bits are zero
110-
// and that n <= `digit_bits`
111-
@[direct_array_access; inline]
112-
fn left_shift_in_place(mut a []u64, n u32) {
113-
mut carry := u64(0)
114-
mut prec_carry := u64(0)
115-
mask := ((u64(1) << n) - 1) << (digit_bits - n)
116-
for index in 0 .. a.len {
117-
prec_carry = carry >> (digit_bits - n)
118-
carry = a[index] & mask
119-
a[index] <<= n
120-
a[index] = a[index] & max_digit
121-
a[index] |= prec_carry
55+
v_n_1 := v[n - 1]
56+
v_n_2 := v[n - 2]
57+
for j := m; j >= 0; j-- {
58+
u_j_n := u[j + n]
59+
u_j_n_1 := u[j + n - 1]
60+
u_j_n_2 := u[j + n - 2]
61+
62+
mut qhat, mut rhat := bits.div_64(u_j_n >> (64 - digit_bits), (u_j_n << digit_bits) | u_j_n_1,
63+
v_n_1)
64+
mut x1, mut x2 := bits.mul_64(qhat, v_n_2)
65+
x2 = x2 & max_digit
66+
x1 = (x1 << (64 - digit_bits)) | (x2 >> digit_bits)
67+
for greater_than(x1, x2, rhat, u_j_n_2) {
68+
qhat--
69+
prev := rhat
70+
rhat += v_n_1
71+
if rhat < prev {
72+
break
73+
}
74+
x1, x2 = bits.mul_64(qhat, v_n_2)
75+
x2 = x2 & max_digit
76+
x1 = (x1 << (64 - digit_bits)) | (x2 >> digit_bits)
77+
}
78+
mut carry := u64(0)
79+
for i in 0 .. n {
80+
hi, lo := bits.mul_add_64(v[i], qhat, carry)
81+
remainder[i] = lo & max_digit
82+
carry = (hi << (64 - digit_bits)) | (lo >> digit_bits)
83+
}
84+
remainder[n] = carry
85+
86+
mut borrow := u64(0)
87+
for i in 0 .. n + 1 {
88+
result := u[j + i] - remainder[i] - borrow
89+
u[j + i] = result & max_digit
90+
borrow = (result >> digit_bits) & 1
91+
}
92+
if borrow == 1 {
93+
qhat--
94+
carry = u64(0)
95+
for i in 0 .. n {
96+
sum := u[j + i] + v[i] + carry
97+
u[j + i] = sum & max_digit
98+
carry = sum >> digit_bits
99+
}
100+
}
101+
quotient[j] = qhat
122102
}
123-
}
124103

125-
// logical right shift without control because these digits have already been
126-
// shift left before
127-
@[direct_array_access; inline]
128-
fn right_shift_in_place(mut a []u64, n u32) {
129-
mut carry := u64(0)
130-
mut prec_carry := u64(0)
131-
mask := (u64(1) << n) - 1
132-
for index := a.len - 1; index >= 0; index-- {
133-
carry = a[index] & mask
134-
a[index] >>= n
135-
a[index] |= prec_carry << (digit_bits - n)
136-
prec_carry = carry
104+
remainder.delete_last()
105+
if leading_zeros > 0 {
106+
mut carry := u64(0)
107+
max_leading_digit := (u64(1) << leading_zeros) - 1
108+
for i := n - 1; i >= 0; i-- {
109+
current_limb := u[i]
110+
remainder[i] = (current_limb >> leading_zeros) | carry
111+
carry = (current_limb & max_leading_digit) << (digit_bits - leading_zeros)
112+
}
113+
} else {
114+
for i in 0 .. n {
115+
remainder[i] = u[i]
116+
}
137117
}
118+
shrink_tail_zeros(mut quotient)
119+
shrink_tail_zeros(mut remainder)
138120
}
139121

140-
// for assert
141122
@[inline]
142-
fn left_align_p(a u64, b u64) bool {
143-
return bits.leading_zeros_64(a) == bits.leading_zeros_64(b)
123+
fn greater_than(x1 u64, x2 u64, y1 u64, y2 u64) bool {
124+
return x1 > y1 || (x1 == y1 && x2 > y2)
144125
}

0 commit comments

Comments
 (0)