Skip to content

Commit

Permalink
math.big: add checked division methods (#18924)
Browse files Browse the repository at this point in the history
  • Loading branch information
phoreverpheebs committed Jul 21, 2023
1 parent 4a543c5 commit 7b306e9
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 11 deletions.
5 changes: 5 additions & 0 deletions vlib/math/big/big_test.v
Expand Up @@ -573,6 +573,11 @@ fn test_div_mod() {
assert q == eq
assert r == er
}

// an extra test for checked division by zero
if _, _ := div_mod_test_data[0].dividend.parse().div_mod_checked(TestInteger(0).parse()) {
assert false, 'Division by 0 should return an error'
}
}

fn test_comparison() {
Expand Down
74 changes: 64 additions & 10 deletions vlib/math/big/integer.v
Expand Up @@ -369,24 +369,28 @@ pub fn (multiplicand Integer) * (multiplier Integer) Integer {
}
}

// div_mod returns the quotient and remainder from the division of the integers `dividend` divided by `divisor`.
pub fn (dividend Integer) div_mod(divisor Integer) (Integer, Integer) {
// Quick exits
if divisor.signum == 0 {
panic('Cannot divide by zero')
// div_mod_internal is an entirely unchecked (in terms of division by zero) method for division.
// This should only be used for internal calculations involving a definitive non-zero
// divisor.
//
// DO NOT use this method if the divisor has any chance of being 0.
fn (dividend Integer) div_mod_internal(divisor Integer) (Integer, Integer) {
$if debug {
assert divisor.signum != 0
}

if dividend.signum == 0 {
return zero_int, zero_int
}
if divisor == one_int {
return dividend.clone(), zero_int
}
if divisor.signum == -1 {
q, r := dividend.div_mod(divisor.neg())
q, r := dividend.div_mod_internal(divisor.neg())
return q.neg(), r
}
if dividend.signum == -1 {
q, r := dividend.neg().div_mod(divisor)
q, r := dividend.neg().div_mod_internal(divisor)
if r.signum == 0 {
return q.neg(), zero_int
} else {
Expand All @@ -408,18 +412,64 @@ pub fn (dividend Integer) div_mod(divisor Integer) (Integer, Integer) {
return quotient, remainder
}

// div_mod returns the quotient and remainder from the division of the integers `dividend`
// divided by `divisor`.
//
// WARNING: this method will panic if `divisor == 0`. Refer to div_mod_checked for a safer version.
[inline]
pub fn (dividend Integer) div_mod(divisor Integer) (Integer, Integer) {
if _unlikely_(divisor.signum == 0) {
panic('math.big: Cannot divide by zero')
}
return dividend.div_mod_internal(divisor)
}

// div_mod_checked returns the quotient and remainder from the division of the integers `dividend`
// divided by `divisor`. An error is returned if `divisor == 0`.
[inline]
pub fn (dividend Integer) div_mod_checked(divisor Integer) !(Integer, Integer) {
if _unlikely_(divisor.signum == 0) {
return error('math.big: Cannot divide by zero')
}
return dividend.div_mod_internal(divisor)
}

// / returns the quotient of `dividend` divided by `divisor`.
//
// WARNING: this method will panic if `divisor == 0`. For a division method that returns a Result
// refer to `div_checked`.
[inline]
pub fn (dividend Integer) / (divisor Integer) Integer {
q, _ := dividend.div_mod(divisor)
return q
}

// % returns the remainder of `dividend` divided by `divisor`.
//
// WARNING: this method will panic if `divisor == 0`. For a modular division method that
// returns a Result refer to `mod_checked`.
[inline]
pub fn (dividend Integer) % (divisor Integer) Integer {
_, r := dividend.div_mod(divisor)
return r
}

// div_checked returns the quotient of `dividend` divided by `divisor`
// or an error if `divisor == 0`.
[inline]
pub fn (dividend Integer) div_checked(divisor Integer) !Integer {
q, _ := dividend.div_mod_checked(divisor)!
return q
}

// mod_checked returns the remainder of `dividend` divided by `divisor`
// or an error if `divisor == 0`.
[inline]
pub fn (dividend Integer) mod_checked(divisor Integer) !Integer {
_, r := dividend.div_mod_checked(divisor)!
return r
}

// mask_bits is the equivalent of `a % 2^n` (only when `a >= 0`), however doing a full division
// run for this would be a lot of work when we can simply "cut off" all bits to the left of
// the `n`th bit.
Expand Down Expand Up @@ -791,7 +841,7 @@ pub fn (integer Integer) hex() string {

// radix_str returns the string representation of the integer `a` in the specified radix.
pub fn (integer Integer) radix_str(radix u32) string {
if integer.signum == 0 {
if integer.signum == 0 || radix == 0 {
return '0'
}
return match radix {
Expand All @@ -808,14 +858,17 @@ pub fn (integer Integer) radix_str(radix u32) string {
}

fn (integer Integer) general_radix_str(radix u32) string {
$if debug {
assert radix != 0
}
divisor := integer_from_u32(radix)

mut current := integer.abs()
mut new_current := zero_int
mut digit := zero_int
mut rune_array := []rune{cap: current.digits.len * 4}
for current.signum > 0 {
new_current, digit = current.div_mod(divisor)
new_current, digit = current.div_mod_internal(divisor)
rune_array << big.digit_array[digit.int()]
unsafe { digit.free() }
unsafe { current.free() }
Expand Down Expand Up @@ -1034,7 +1087,8 @@ fn (a Integer) mod_inv(m Integer) Integer {
q, r := if n.bit_len() == b.bit_len() {
one_int, n - b
} else {
n.div_mod(b)
// safe because the loop terminates if b == 0
n.div_mod_internal(b)
}

n = b
Expand Down
2 changes: 1 addition & 1 deletion vlib/math/big/special_array_ops.v
Expand Up @@ -228,7 +228,7 @@ fn toom3_multiply_digit_array(operand_a []u32, operand_b []u32, mut storage []u3
p2 := ((ptemp + a2).left_shift(1) - a0) * ((qtemp + b2).left_shift(1) - b0)
pinf := a2 * b2

mut t2 := (p2 - vm1) / three_int
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)
Expand Down

0 comments on commit 7b306e9

Please sign in to comment.