-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ext/bcmath: Simplify bc_divide()
code
#17987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
884e909
19e09bb
d016743
da0916c
8ea5dd6
8ecf974
56a9028
1c9199d
5ed3c74
ff28c14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,4 +57,32 @@ static inline void bc_convert_to_vector(BC_VECTOR *n_vector, const char *nend, s | |
} | ||
} | ||
|
||
static inline void bc_convert_to_vector_with_zero_pad(BC_VECTOR *n_vector, const char *nend, size_t nlen, size_t zeros) | ||
{ | ||
while (zeros >= BC_VECTOR_SIZE) { | ||
*n_vector = 0; | ||
n_vector++; | ||
zeros -= BC_VECTOR_SIZE; | ||
} | ||
|
||
if (zeros > 0) { | ||
*n_vector = 0; | ||
BC_VECTOR base = BC_POW_10_LUT[zeros]; | ||
size_t tmp_len = MIN(BC_VECTOR_SIZE - zeros, nlen); | ||
for (size_t i = 0; i < tmp_len; i++) { | ||
*n_vector += *nend * base; | ||
base *= BASE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to check: this multiplication can't overflow because the highest it can ever get is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed in 1c9199d
Yes, that's right. |
||
nend--; | ||
} | ||
n_vector++; | ||
nlen -= tmp_len; | ||
} | ||
|
||
if (nlen == 0) { | ||
return; | ||
} | ||
|
||
bc_convert_to_vector(n_vector, nend, nlen); | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,10 +37,6 @@ | |
#include <string.h> | ||
#include "zend_alloc.h" | ||
|
||
static const BC_VECTOR POW_10_LUT[9] = { | ||
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 | ||
}; | ||
|
||
/* | ||
* This function should be used when the divisor is not split into multiple chunks, i.e. when the size of the array is one. | ||
* This is because the algorithm can be simplified. | ||
|
@@ -51,7 +47,7 @@ static inline void bc_fast_div( | |
{ | ||
size_t numerator_top_index = numerator_arr_size - 1; | ||
size_t quot_top_index = quot_arr_size - 1; | ||
for (size_t i = 0; i < quot_arr_size - 1; i++) { | ||
for (size_t i = 0; i < quot_top_index; i++) { | ||
if (numerator_vectors[numerator_top_index - i] < divisor_vector) { | ||
quot_vectors[quot_top_index - i] = 0; | ||
/* numerator_vectors[numerator_top_index - i] < divisor_vector, so there will be no overflow. */ | ||
|
@@ -174,8 +170,8 @@ static inline void bc_standard_div( | |
divisor_top_digits = BC_VECTOR_SIZE; | ||
} | ||
|
||
size_t high_part_shift = POW_10_LUT[BC_VECTOR_SIZE - divisor_top_digits + 1]; | ||
size_t low_part_shift = POW_10_LUT[divisor_top_digits - 1]; | ||
size_t high_part_shift = BC_POW_10_LUT[BC_VECTOR_SIZE - divisor_top_digits + 1]; | ||
size_t low_part_shift = BC_POW_10_LUT[divisor_top_digits - 1]; | ||
BC_VECTOR divisor_high_part = divisor_vectors[divisor_top_index] * high_part_shift + divisor_vectors[divisor_top_index - 1] / low_part_shift; | ||
for (size_t i = 0; i < quot_arr_size; i++) { | ||
BC_VECTOR numerator_high_part = numerator_vectors[numerator_top_index - i] * high_part_shift + numerator_vectors[numerator_top_index - i - 1] / low_part_shift; | ||
|
@@ -255,58 +251,39 @@ static inline void bc_standard_div( | |
} | ||
|
||
static void bc_do_div( | ||
const char *numerator, size_t numerator_readable_len, size_t numerator_bottom_extension, | ||
const char *divisor, size_t divisor_len, bc_num *quot, size_t quot_len | ||
const char *numerator, size_t numerator_size, size_t numerator_readable_size, | ||
const char *divisor, size_t divisor_size, | ||
bc_num *quot, size_t quot_size | ||
) { | ||
size_t divisor_arr_size = (divisor_len + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE; | ||
size_t numerator_arr_size = (numerator_readable_len + numerator_bottom_extension + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE; | ||
size_t numerator_arr_size = (numerator_size + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE; | ||
size_t divisor_arr_size = (divisor_size + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE; | ||
size_t quot_arr_size = numerator_arr_size - divisor_arr_size + 1; | ||
size_t quot_real_arr_size = MIN(quot_arr_size, (quot_len + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE); | ||
size_t quot_real_arr_size = MIN(quot_arr_size, (quot_size + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE); | ||
|
||
BC_VECTOR *numerator_vectors = safe_emalloc(numerator_arr_size + divisor_arr_size + quot_arr_size, sizeof(BC_VECTOR), 0); | ||
BC_VECTOR *divisor_vectors = numerator_vectors + numerator_arr_size; | ||
BC_VECTOR *quot_vectors = divisor_vectors + divisor_arr_size; | ||
|
||
/* Fill with zeros and convert as many vector elements as needed */ | ||
size_t numerator_vector_count = 0; | ||
while (numerator_bottom_extension >= BC_VECTOR_SIZE) { | ||
numerator_vectors[numerator_vector_count] = 0; | ||
numerator_bottom_extension -= BC_VECTOR_SIZE; | ||
numerator_vector_count++; | ||
} | ||
|
||
size_t numerator_bottom_read_len = BC_VECTOR_SIZE - numerator_bottom_extension; | ||
|
||
size_t base; | ||
size_t numerator_read = 0; | ||
if (numerator_bottom_read_len < BC_VECTOR_SIZE) { | ||
numerator_read = MIN(numerator_bottom_read_len, numerator_readable_len); | ||
base = POW_10_LUT[numerator_bottom_extension]; | ||
numerator_vectors[numerator_vector_count] = 0; | ||
for (size_t i = 0; i < numerator_read; i++) { | ||
numerator_vectors[numerator_vector_count] += *numerator * base; | ||
base *= BASE; | ||
numerator--; | ||
} | ||
numerator_vector_count++; | ||
} | ||
size_t numerator_extension = numerator_size > numerator_readable_size ? numerator_size - numerator_readable_size : 0; | ||
|
||
/* Bulk convert numerator and divisor to vectors */ | ||
if (numerator_readable_len > numerator_read) { | ||
bc_convert_to_vector(numerator_vectors + numerator_vector_count, numerator, numerator_readable_len - numerator_read); | ||
} | ||
bc_convert_to_vector(divisor_vectors, divisor, divisor_len); | ||
size_t numerator_use_size = numerator_size - numerator_extension; | ||
const char *numerator_end = numerator + numerator_use_size - 1; | ||
bc_convert_to_vector_with_zero_pad(numerator_vectors, numerator_end, numerator_use_size, numerator_extension); | ||
|
||
const char *divisor_end = divisor + divisor_size - 1; | ||
bc_convert_to_vector(divisor_vectors, divisor_end, divisor_size); | ||
|
||
/* Do the division */ | ||
if (divisor_arr_size == 1) { | ||
bc_fast_div(numerator_vectors, numerator_arr_size, divisor_vectors[0], quot_vectors, quot_arr_size); | ||
} else { | ||
bc_standard_div(numerator_vectors, numerator_arr_size, divisor_vectors, divisor_arr_size, divisor_len, quot_vectors, quot_arr_size); | ||
bc_standard_div(numerator_vectors, numerator_arr_size, divisor_vectors, divisor_arr_size, divisor_size, quot_vectors, quot_arr_size); | ||
} | ||
|
||
/* Convert to bc_num */ | ||
char *qptr = (*quot)->n_value; | ||
char *qend = qptr + quot_len - 1; | ||
char *qend = qptr + (*quot)->n_len + (*quot)->n_scale - 1; | ||
|
||
size_t i; | ||
for (i = 0; i < quot_real_arr_size - 1; i++) { | ||
|
@@ -328,6 +305,42 @@ static void bc_do_div( | |
efree(numerator_vectors); | ||
} | ||
|
||
static inline void bc_divide_by_one(bc_num numerator, bc_num *quot, size_t quot_scale) | ||
{ | ||
quot_scale = MIN(numerator->n_scale, quot_scale); | ||
*quot = bc_new_num_nonzeroed(numerator->n_len, quot_scale); | ||
char *qptr = (*quot)->n_value; | ||
memcpy(qptr, numerator->n_value, numerator->n_len + quot_scale); | ||
} | ||
|
||
static inline void bc_divide_by_pow_10( | ||
const char *numeratorptr, size_t numerator_readable_size, bc_num *quot, size_t quot_size, size_t quot_scale) | ||
{ | ||
char *qptr = (*quot)->n_value; | ||
if (quot_size <= quot_scale) { | ||
/* int is 0 */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this comment mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your simpler code makes this comment unnecessary, but just to be clear: |
||
*qptr++ = 0; | ||
for (size_t i = quot_size; i < quot_scale; i++) { | ||
*qptr++ = 0; | ||
} | ||
} | ||
SakiTakamachi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
size_t numerator_use_size = quot_size > numerator_readable_size ? numerator_readable_size : quot_size; | ||
memcpy(qptr, numeratorptr, numerator_use_size); | ||
qptr += numerator_use_size; | ||
|
||
if (numerator_use_size < (*quot)->n_len) { | ||
/* e.g. 12.3 / 0.01 <=> 1230 */ | ||
for (size_t i = numerator_use_size; i < (*quot)->n_len; i++) { | ||
*qptr++ = 0; | ||
} | ||
(*quot)->n_scale = 0; | ||
} else { | ||
char *qend = (*quot)->n_value + (*quot)->n_len + (*quot)->n_scale; | ||
(*quot)->n_scale -= qend - qptr; | ||
} | ||
} | ||
|
||
bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) | ||
{ | ||
/* divide by zero */ | ||
|
@@ -336,166 +349,98 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) | |
} | ||
|
||
bc_free_num(quot); | ||
size_t quot_scale = scale; | ||
|
||
/* If numerator is zero, the quotient is always zero. */ | ||
if (bc_is_zero(numerator)) { | ||
*quot = bc_copy_num(BCG(_zero_)); | ||
return true; | ||
goto quot_zero; | ||
} | ||
|
||
/* If divisor is 1 / -1, the quotient's n_value is equal to numerator's n_value. */ | ||
if (_bc_do_compare(divisor, BCG(_one_), divisor->n_scale, false) == BCMATH_EQUAL) { | ||
size_t quot_scale = MIN(numerator->n_scale, scale); | ||
*quot = bc_new_num_nonzeroed(numerator->n_len, quot_scale); | ||
char *qptr = (*quot)->n_value; | ||
memcpy(qptr, numerator->n_value, numerator->n_len + quot_scale); | ||
bc_divide_by_one(numerator, quot, quot_scale); | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
_bc_rm_leading_zeros(*quot); | ||
return true; | ||
} | ||
|
||
char *numeratorptr = numerator->n_value; | ||
char *numeratorend = numeratorptr + numerator->n_len + numerator->n_scale - 1; | ||
size_t numerator_len = numerator->n_len; | ||
size_t numerator_scale = numerator->n_scale; | ||
size_t numerator_size = numerator->n_len + quot_scale + divisor->n_scale; | ||
|
||
char *divisorptr = divisor->n_value; | ||
char *divisorend = divisorptr + divisor->n_len + divisor->n_scale - 1; | ||
size_t divisor_len = divisor->n_len; | ||
size_t divisor_scale = divisor->n_scale; | ||
size_t divisor_int_right_zeros = 0; | ||
|
||
/* remove divisor trailing zeros */ | ||
while (*divisorend == 0 && divisor_scale > 0) { | ||
divisorend--; | ||
divisor_scale--; | ||
} | ||
while (*divisorend == 0) { | ||
divisorend--; | ||
divisor_int_right_zeros++; | ||
} | ||
size_t divisor_size = divisor->n_len + divisor->n_scale; | ||
|
||
if (*numeratorptr == 0 && numerator_len == 1) { | ||
/* check and remove numerator leading zeros */ | ||
size_t numerator_leading_zeros = 0; | ||
while (*numeratorptr == 0) { | ||
numeratorptr++; | ||
numerator_len = 0; | ||
numerator_leading_zeros++; | ||
} | ||
|
||
size_t numerator_top_extension = 0; | ||
size_t numerator_bottom_extension = 0; | ||
if (divisor_scale > 0) { | ||
/* | ||
* e.g. divisor_scale = 4 | ||
* divisor = .0002, to be 2 or divisor = 200.001, to be 200001 | ||
* numerator = .03, to be 300 or numerator = .000003, to be .03 | ||
* numerator may become longer than the original data length due to the addition of | ||
* trailing zeros in the integer part. | ||
*/ | ||
numerator_len += divisor_scale; | ||
numerator_bottom_extension = numerator_scale < divisor_scale ? divisor_scale - numerator_scale : 0; | ||
numerator_scale = numerator_scale > divisor_scale ? numerator_scale - divisor_scale : 0; | ||
divisor_len += divisor_scale; | ||
divisor_scale = 0; | ||
} else if (divisor_int_right_zeros > 0) { | ||
/* | ||
* e.g. divisor_int_right_zeros = 4 | ||
* divisor = 2000, to be 2 | ||
* numerator = 30, to be .03 or numerator = 30000, to be 30 | ||
* Also, numerator may become longer than the original data length due to the addition of | ||
* leading zeros in the fractional part. | ||
*/ | ||
numerator_top_extension = numerator_len < divisor_int_right_zeros ? divisor_int_right_zeros - numerator_len : 0; | ||
numerator_len = numerator_len > divisor_int_right_zeros ? numerator_len - divisor_int_right_zeros : 0; | ||
numerator_scale += divisor_int_right_zeros; | ||
divisor_len -= divisor_int_right_zeros; | ||
divisor_scale = 0; | ||
if (numerator_size > numerator_leading_zeros) { | ||
numerator_size -= numerator_leading_zeros; | ||
} else { | ||
goto quot_zero; | ||
} | ||
|
||
/* remove numerator leading zeros */ | ||
while (*numeratorptr == 0 && numerator_len > 0) { | ||
numeratorptr++; | ||
numerator_len--; | ||
} | ||
/* remove divisor leading zeros */ | ||
/* check and remove divisor leading zeros */ | ||
while (*divisorptr == 0) { | ||
divisorptr++; | ||
divisor_len--; | ||
divisor_size--; | ||
} | ||
|
||
/* Considering the scale specification, the quotient is always 0 if this condition is met */ | ||
if (divisor_len > numerator_len + scale) { | ||
*quot = bc_copy_num(BCG(_zero_)); | ||
return true; | ||
if (divisor_size > numerator_size) { | ||
goto quot_zero; | ||
} | ||
|
||
/* Length of numerator data that can be read */ | ||
size_t numerator_readable_len = numeratorend - numeratorptr + 1; | ||
|
||
/* set scale to numerator */ | ||
if (numerator_scale > scale) { | ||
size_t scale_diff = numerator_scale - scale; | ||
if (numerator_bottom_extension > scale_diff) { | ||
numerator_bottom_extension -= scale_diff; | ||
} else { | ||
numerator_bottom_extension = 0; | ||
if (EXPECTED(numerator_readable_len > scale_diff)) { | ||
numerator_readable_len -= scale_diff; | ||
numeratorend -= scale_diff; | ||
} else { | ||
numerator_readable_len = 0; | ||
numeratorend = numeratorptr; | ||
} | ||
/* check and remove divisor trailing zeros. The divisor is not 0, so leave only one digit */ | ||
size_t divisor_trailing_zeros = 0; | ||
for (size_t i = divisor_size - 1; i > 0; i--) { | ||
if (divisorptr[i] != 0) { | ||
break; | ||
} | ||
numerator_top_extension = MIN(numerator_top_extension, scale); | ||
divisor_trailing_zeros++; | ||
} | ||
divisor_size -= divisor_trailing_zeros; | ||
|
||
if (numerator_size > divisor_trailing_zeros) { | ||
numerator_size -= divisor_trailing_zeros; | ||
} else { | ||
numerator_bottom_extension += scale - numerator_scale; | ||
goto quot_zero; | ||
} | ||
numerator_scale = scale; | ||
|
||
if (divisor_len > numerator_readable_len + numerator_bottom_extension) { | ||
*quot = bc_copy_num(BCG(_zero_)); | ||
return true; | ||
size_t quot_size = numerator_size - divisor_size + 1; /* numerator_size >= divisor_size */ | ||
if (quot_size > quot_scale) { | ||
*quot = bc_new_num_nonzeroed(quot_size - quot_scale, quot_scale); | ||
} else { | ||
*quot = bc_new_num_nonzeroed(1, quot_scale); /* 1 is for 0 */ | ||
} | ||
|
||
/* If divisor is 1 here, return the result of adjusting the decimal point position of numerator. */ | ||
if (divisor_len == 1 && *divisorptr == 1) { | ||
if (numerator_len == 0) { | ||
numerator_len = 1; | ||
numerator_top_extension++; | ||
} | ||
size_t quot_scale = numerator_scale > numerator_bottom_extension ? numerator_scale - numerator_bottom_extension : 0; | ||
numerator_bottom_extension = numerator_scale < numerator_bottom_extension ? numerator_bottom_extension - numerator_scale : 0; | ||
/* Size that can be read from numeratorptr */ | ||
size_t numerator_readable_size = numerator->n_len + numerator->n_scale - numerator_leading_zeros; | ||
|
||
*quot = bc_new_num_nonzeroed(numerator_len, quot_scale); | ||
char *qptr = (*quot)->n_value; | ||
for (size_t i = 0; i < numerator_top_extension; i++) { | ||
*qptr++ = 0; | ||
} | ||
memcpy(qptr, numeratorptr, numerator_readable_len); | ||
qptr += numerator_readable_len; | ||
for (size_t i = 0; i < numerator_bottom_extension; i++) { | ||
*qptr++ = 0; | ||
} | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
/* If divisor is 1 here, return the result of adjusting the decimal point position of numerator. */ | ||
if (divisor_size == 1 && *divisorptr == 1) { | ||
bc_divide_by_pow_10(numeratorptr, numerator_readable_size, quot, quot_size, quot_scale); | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS;; | ||
return true; | ||
} | ||
|
||
size_t quot_full_len; | ||
if (divisor_len > numerator_len) { | ||
*quot = bc_new_num_nonzeroed(1, scale); | ||
quot_full_len = 1 + scale; | ||
} else { | ||
*quot = bc_new_num_nonzeroed(numerator_len - divisor_len + 1, scale); | ||
quot_full_len = numerator_len - divisor_len + 1 + scale; | ||
} | ||
|
||
/* do divide */ | ||
bc_do_div(numeratorend, numerator_readable_len, numerator_bottom_extension, divisorend, divisor_len, quot, quot_full_len); | ||
bc_do_div( | ||
numeratorptr, numerator_size, numerator_readable_size, | ||
divisorptr, divisor_size, | ||
quot, quot_size | ||
); | ||
|
||
_bc_rm_leading_zeros(*quot); | ||
if (bc_is_zero(*quot)) { | ||
(*quot)->n_sign = PLUS; | ||
bc_free_num(quot); | ||
goto quot_zero; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} else { | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
} | ||
return true; | ||
|
||
quot_zero: | ||
*quot = bc_copy_num(BCG(_zero_)); | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please pick a more meaningful variable name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 1c9199d