From 7b306e9b8fe3834c0794d1a5bcc7214e5e450623 Mon Sep 17 00:00:00 2001 From: phoebe Date: Fri, 21 Jul 2023 19:06:54 +0200 Subject: [PATCH] math.big: add checked division methods (#18924) --- vlib/math/big/big_test.v | 5 +++ vlib/math/big/integer.v | 74 ++++++++++++++++++++++++++----- vlib/math/big/special_array_ops.v | 2 +- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/vlib/math/big/big_test.v b/vlib/math/big/big_test.v index 97ec32ab45c30b..fbfb6bde7fc0f1 100644 --- a/vlib/math/big/big_test.v +++ b/vlib/math/big/big_test.v @@ -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() { diff --git a/vlib/math/big/integer.v b/vlib/math/big/integer.v index acf73abb6b9a8d..e7fe59fecbdc2a 100644 --- a/vlib/math/big/integer.v +++ b/vlib/math/big/integer.v @@ -369,12 +369,16 @@ 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 } @@ -382,11 +386,11 @@ pub fn (dividend Integer) div_mod(divisor Integer) (Integer, Integer) { 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 { @@ -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. @@ -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 { @@ -808,6 +858,9 @@ 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() @@ -815,7 +868,7 @@ fn (integer Integer) general_radix_str(radix u32) string { 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() } @@ -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 diff --git a/vlib/math/big/special_array_ops.v b/vlib/math/big/special_array_ops.v index 33291b6b7e5cc3..eb9caae3c7b6df 100644 --- a/vlib/math/big/special_array_ops.v +++ b/vlib/math/big/special_array_ops.v @@ -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)