v/vlib/math/big/special_array_ops.v
Mike 38519eca27
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
math.big: remove Newton-raphson division, remove related tests (#25168)
2025-08-26 04:06:07 +03:00

368 lines
8.5 KiB
V

module big
import strings
@[direct_array_access; inline]
fn shrink_tail_zeros(mut a []u64) {
mut alen := a.len
for alen > 0 && a[alen - 1] == 0 {
alen--
}
unsafe {
a.len = alen
}
}
@[direct_array_access; inline]
fn (i &Integer) shrink_tail_zeros() {
mut alen := i.digits.len
for alen > 0 && i.digits[alen - 1] == 0 {
alen--
}
unsafe {
i.digits.len = alen
}
}
// debug_u64_str output a `[]u64`
@[direct_array_access]
fn debug_u64_str(a []u64) string {
mut sb := strings.new_builder(30)
sb.write_string('[')
mut first := true
for i in 0 .. a.len {
if !first {
sb.write_string(', ')
}
sb.write_string('0x${a[i].hex():016}')
first = false
}
sb.write_string(']')
return sb.str()
}
// debug_u32_str for 32bit bignum test only, convert a `[]u64` to `[]u32`.
@[direct_array_access]
fn debug_u32_str(a []u64) string {
mut b := []u32{cap: a.len * 2}
mut curr_u32 := u32(0)
mut bits_collected := 0
for w in a {
for i in 0 .. digit_bits {
bit := (w >> i) & 1
curr_u32 |= u32(bit) << bits_collected
bits_collected++
if bits_collected == 32 {
b << curr_u32
curr_u32 = 0
bits_collected = 0
}
}
}
if bits_collected > 0 {
b << curr_u32
}
mut blen := b.len
for blen > 0 && b[blen - 1] == 0 {
blen--
}
unsafe {
b.len = blen
}
mut sb := strings.new_builder(30)
sb.write_string('[')
mut first := true
for i in 0 .. b.len {
if !first {
sb.write_string(', ')
}
sb.write_string('0x${b[i].hex():08}')
first = false
}
sb.write_string(']')
return sb.str()
}
@[direct_array_access; inline]
fn found_multiplication_base_case(operand_a []u64, operand_b []u64, mut storage []u64) bool {
// base case necessary to end recursion
if operand_a.len == 0 || operand_b.len == 0 {
storage.clear()
return true
}
if operand_a.len < operand_b.len {
multiply_digit_array(operand_b, operand_a, mut storage)
return true
}
if operand_b.len == 1 {
multiply_array_by_digit(operand_a, operand_b[0], mut storage)
return true
}
return false
}
// karatsuba algorithm for multiplication
// possible optimisations:
// - transform one or all the recurrences in loops
@[direct_array_access]
fn karatsuba_multiply_digit_array(operand_a []u64, operand_b []u64, mut storage []u64) {
if found_multiplication_base_case(operand_a, operand_b, mut storage) {
return
}
// thanks to the base cases we can pass zero-length arrays to the mult func
half := imax(operand_a.len, operand_b.len) / 2
mut a_l := unsafe { operand_a[0..half] }
mut a_h := unsafe { operand_a[half..] }
mut b_l := []u64{}
mut b_h := []u64{}
if half <= operand_b.len {
b_l = unsafe { operand_b[0..half] }
b_h = unsafe { operand_b[half..] }
} else {
b_l = unsafe { operand_b }
// b_h = []u64{}
}
shrink_tail_zeros(mut a_l)
shrink_tail_zeros(mut a_h)
shrink_tail_zeros(mut b_l)
shrink_tail_zeros(mut b_h)
// use storage for p_1 to avoid allocation and copy later
multiply_digit_array(a_h, b_h, mut storage)
mut p_3 := []u64{len: a_l.len + b_l.len + 1}
multiply_digit_array(a_l, b_l, mut p_3)
mut tmp_1 := []u64{len: imax(a_h.len, a_l.len) + 1}
mut tmp_2 := []u64{len: imax(b_h.len, b_l.len) + 1}
add_digit_array(a_h, a_l, mut tmp_1)
add_digit_array(b_h, b_l, mut tmp_2)
mut p_2 := []u64{len: operand_a.len + operand_b.len + 1}
multiply_digit_array(tmp_1, tmp_2, mut p_2)
subtract_in_place(mut p_2, storage) // p_1
subtract_in_place(mut p_2, p_3)
// return p_1.left_shift(2 * u32(half * 32)) + p_2.left_shift(u32(half * 32)) + p_3
left_shift_digits_in_place(mut storage, 2 * half)
left_shift_digits_in_place(mut p_2, half)
add_in_place(mut storage, p_2)
add_in_place(mut storage, p_3)
shrink_tail_zeros(mut storage)
}
// TODO: the manualfree tag here is a workaround for compilation with -autofree. Remove it, when the -autofree bug is fixed.
@[direct_array_access; manualfree]
fn toom3_multiply_digit_array(operand_a []u64, operand_b []u64, mut storage []u64) {
if found_multiplication_base_case(operand_a, operand_b, mut storage) {
return
}
// After the base case, we have operand_a as the larger integer in terms of digit length
// k is the length (in u64 digits) of the lower order slices
k := (operand_a.len + 2) / 3
k2 := 2 * k
// The pieces of the calculation need to be worked on as proper big.Integers
// because the intermediate results can be negative. After recombination, the
// final result will be positive.
// Slices of a and b
a0 := Integer{
digits: unsafe { operand_a[..k] }
signum: if operand_a[..k].all(it == 0) {
0
} else {
1
}
}
a0.shrink_tail_zeros()
a1 := Integer{
digits: unsafe { operand_a[k..k2] }
signum: if operand_a[k..k2].all(it == 0) {
0
} else {
1
}
}
a1.shrink_tail_zeros()
a2 := Integer{
digits: unsafe { operand_a[k2..] }
signum: 1
}
// Zero arrays by default
mut b0 := zero_int.clone()
mut b1 := zero_int.clone()
mut b2 := zero_int.clone()
if operand_b.len < k {
b0 = Integer{
digits: operand_b
signum: 1
}
} else if operand_b.len < k2 {
if !operand_b[..k].all(it == 0) {
b0 = Integer{
digits: operand_b[..k].clone()
signum: 1
}
}
b0.shrink_tail_zeros()
b1 = Integer{
digits: operand_b[k..].clone()
signum: 1
}
} else {
if !operand_b[..k].all(it == 0) {
b0 = Integer{
digits: operand_b[..k].clone()
signum: 1
}
}
b0.shrink_tail_zeros()
if !operand_b[k..k2].all(it == 0) {
b1 = Integer{
digits: operand_b[k..k2].clone()
signum: 1
}
}
b1.shrink_tail_zeros()
b2 = Integer{
digits: operand_b[k2..].clone()
signum: 1
}
}
// https://en.wikipedia.org/wiki/Toom%E2%80%93Cook_multiplication#Details
// DOI: 10.1007/978-3-540-73074-3_10
p0 := a0 * b0
mut ptemp := a2 + a0
mut qtemp := b2 + b0
vm1 := (ptemp - a1) * (qtemp - b1)
ptemp += a1
qtemp += b1
p1 := ptemp * qtemp
p2 := ((ptemp + a2).left_shift(1) - a0) * ((qtemp + b2).left_shift(1) - b0)
pinf := a2 * b2
mut t2, _ := (p2 - vm1).div_mod_internal(three_int)
mut tm1 := (p1 - vm1).right_shift(1)
mut t1 := p1 - p0
t2 = (t2 - t1).right_shift(1)
t1 = (t1 - tm1 - pinf)
t2 = t2 - pinf.left_shift(1)
tm1 = tm1 - t2
// shift amount
s := u32(k) * digit_bits
result := (((pinf.left_shift(s) + t2).left_shift(s) + t1).left_shift(s) + tm1).left_shift(s) +
p0
storage = result.digits.clone()
}
@[inline]
fn pow2(k int) Integer {
mut ret := []u64{len: (k / digit_bits) + 1}
bit_set(mut ret, k)
return Integer{
signum: 1
digits: ret
}
}
// optimized left shift in place. amount must be positive
fn left_shift_digits_in_place(mut a []u64, amount int) {
// this is actual in builtin/array.v, prepend_many (private fn)
// x := []u64{ len : amount }
// a.prepend_many(&x[0], amount)
old_len := a.len
elem_size := a.element_size
unsafe {
a.grow_len(amount)
sptr := &u8(a.data)
dptr := &u8(a.data) + u64(amount) * u64(elem_size)
vmemmove(dptr, sptr, u64(old_len) * u64(elem_size))
vmemset(sptr, 0, u64(amount) * u64(elem_size))
}
}
// optimized right shift in place. amount must be positive
fn right_shift_digits_in_place(mut a []u64, amount int) {
a.drop(amount)
}
// operand b can be greater than operand a
// the capacity of both array is supposed to be sufficient
@[direct_array_access; inline]
fn add_in_place(mut a []u64, b []u64) {
len_a := a.len
len_b := b.len
max := imax(len_a, len_b)
min := imin(len_a, len_b)
mut carry := u64(0)
for index in 0 .. min {
partial := carry + a[index] + b[index]
a[index] = u64(partial) & max_digit
carry = u64(partial >> digit_bits)
}
if len_a >= len_b {
for index in min .. max {
partial := carry + a[index]
a[index] = u64(partial) & max_digit
carry = u64(partial >> digit_bits)
}
} else {
for index in min .. max {
partial := carry + b[index]
a << u64(partial) & max_digit
carry = u64(partial >> digit_bits)
}
}
if carry > 0 {
a << carry
}
}
// a := a - b supposed a >= b
@[direct_array_access; inline]
fn subtract_in_place(mut a []u64, b []u64) {
len_a := a.len
len_b := b.len
max := imax(len_a, len_b)
min := imin(len_a, len_b)
mut borrow := false
for index in 0 .. min {
mut a_digit := a[index]
b_digit := b[index] + if borrow { u64(1) } else { u64(0) }
borrow = a_digit < b_digit
if borrow {
a_digit = a_digit | (u64(1) << digit_bits)
}
a[index] = a_digit - b_digit
}
if len_a >= len_b {
for index in min .. max {
mut a_digit := a[index]
b_digit := if borrow { u64(1) } else { u64(0) }
borrow = a_digit < b_digit
if borrow {
a_digit = a_digit | (u64(1) << digit_bits)
}
a[index] = a_digit - b_digit
}
} else { // if len.b > len.a return zero
a.clear()
}
}