-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ext/bcmath: optimized divmod()
and mod()
take 2
#18058
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
base: master
Are you sure you want to change the base?
Changes from all commits
03955d6
aaf1f6a
b2c1b3f
c0000cd
0865562
5e4b175
13a98ae
b949c5e
484d9df
2d63079
c7916b5
a2ba77c
d3a5713
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 |
---|---|---|
|
@@ -61,6 +61,7 @@ static inline void bc_fast_div( | |
} | ||
/* last */ | ||
quot_vectors[0] = numerator_vectors[0] / divisor_vector; | ||
numerator_vectors[0] -= divisor_vector * quot_vectors[0]; | ||
} | ||
|
||
/* | ||
|
@@ -248,12 +249,15 @@ static inline void bc_standard_div( | |
div_carry = numerator_vectors[numerator_top_index - i]; | ||
numerator_vectors[numerator_top_index - i] = 0; | ||
} | ||
numerator_vectors[numerator_top_index - quot_arr_size + 1] = div_carry; | ||
} | ||
|
||
static void bc_do_div( | ||
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 | ||
bc_num *quot, size_t quot_size, | ||
bc_num *rem, size_t rem_over_size, size_t rem_write_size, | ||
bool use_quot, bool use_rem | ||
) { | ||
size_t numerator_arr_size = BC_ARR_SIZE_FROM_LEN(numerator_size); | ||
size_t divisor_arr_size = BC_ARR_SIZE_FROM_LEN(divisor_size); | ||
|
@@ -290,57 +294,134 @@ static void bc_do_div( | |
} | ||
|
||
/* Convert to bc_num */ | ||
char *qptr = (*quot)->n_value; | ||
char *qend = qptr + (*quot)->n_len + (*quot)->n_scale - 1; | ||
if (use_quot) { | ||
char *qptr = (*quot)->n_value; | ||
char *qend = qptr + (*quot)->n_len + (*quot)->n_scale - 1; | ||
bc_convert_vector_to_char(quot_vectors, qptr, qend, quot_real_arr_size); | ||
} | ||
if (use_rem) { | ||
char *rptr = (*rem)->n_value; | ||
char *rend = rptr + rem_write_size - 1; | ||
|
||
size_t rem_arr_size = (rem_write_size + rem_over_size + BC_VECTOR_SIZE - 1) / BC_VECTOR_SIZE; | ||
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. Didn't we have a macro for the rounding-up divide ? |
||
if (UNEXPECTED(rem_arr_size > numerator_arr_size)) { | ||
/* If numerator_arr_size is exceeded because the integer part is zero */ | ||
rem_arr_size = numerator_arr_size; | ||
*rptr = 0; | ||
} | ||
BC_VECTOR *rem_vectors = numerator_vectors; | ||
|
||
bc_convert_vector_to_char(quot_vectors, qptr, qend, quot_real_arr_size); | ||
if (rem_over_size > 0) { | ||
bc_convert_vector_to_char_with_skip(rem_vectors, rptr, rend, rem_arr_size, rem_over_size); | ||
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. I still don't understand the skip |
||
} else { | ||
bc_convert_vector_to_char(rem_vectors, rptr, rend, rem_arr_size); | ||
} | ||
} | ||
|
||
if (allocation_arr_size > BC_STACK_VECTOR_SIZE) { | ||
efree(numerator_vectors); | ||
} | ||
} | ||
|
||
static inline void bc_divide_by_one(bc_num numerator, bc_num *quot, size_t quot_scale) | ||
static inline void bc_divide_copy_numerator(bc_num numerator, bc_num *num, size_t 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); | ||
scale = MIN(numerator->n_scale, scale); | ||
*num = bc_new_num_nonzeroed(numerator->n_len, scale); | ||
memcpy((*num)->n_value, numerator->n_value, numerator->n_len + 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) | ||
static inline void bc_divide_by_one( | ||
bc_num numerator, bc_num divisor, bc_num *quot, bc_num *rem, | ||
size_t quot_scale, size_t rem_scale, bool use_quot, bool use_rem) | ||
{ | ||
char *qptr = (*quot)->n_value; | ||
for (size_t i = quot_size; i <= quot_scale; i++) { | ||
*qptr++ = 0; | ||
if (use_quot) { | ||
bc_divide_copy_numerator(numerator, quot, quot_scale); | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
} | ||
if (use_rem) { | ||
/* When dividing by 1, the integer part of rem is always 0. */ | ||
rem_scale = MIN(numerator->n_scale, rem_scale); | ||
if (rem_scale == 0) { | ||
*rem = bc_copy_num(BCG(_zero_)); | ||
} else { | ||
*rem = bc_new_num_nonzeroed(1, rem_scale); /* 1 is for 0 */ | ||
(*rem)->n_value[0] = 0; | ||
/* copy fractional part */ | ||
memcpy((*rem)->n_value + 1, numerator->n_value + numerator->n_len, rem_scale); | ||
if (bc_is_zero(*rem)) { | ||
(*rem)->n_sign = PLUS; | ||
(*rem)->n_scale = 0; | ||
} else { | ||
(*rem)->n_sign = numerator->n_sign; | ||
} | ||
} | ||
} | ||
} | ||
|
||
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++) { | ||
static inline void bc_divide_by_pow_10( | ||
const char *numeratorptr, size_t numerator_readable_size, size_t numerator_leading_zeros, | ||
bc_num *quot, size_t quot_size, size_t quot_scale, bool use_quot, | ||
bc_num *rem, size_t rem_size, bool use_rem, | ||
size_t numerator_rem_len_diff) | ||
{ | ||
if (use_quot) { | ||
char *qptr = (*quot)->n_value; | ||
for (size_t i = quot_size; i <= quot_scale; i++) { | ||
*qptr++ = 0; | ||
} | ||
(*quot)->n_scale = 0; | ||
} else { | ||
char *qend = (*quot)->n_value + (*quot)->n_len + (*quot)->n_scale; | ||
(*quot)->n_scale -= qend - qptr; | ||
|
||
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; | ||
} | ||
} | ||
if (use_rem) { | ||
size_t rem_leading_zeros = numerator_leading_zeros + quot_size - numerator_rem_len_diff; | ||
if (rem_size <= rem_leading_zeros) { | ||
bc_free_num(rem); | ||
*rem = bc_copy_num(BCG(_zero_)); | ||
return; | ||
} | ||
/* The values after this have already been copied, so just need to set them to 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. Copied by whom? 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. About 10 lines above the line where this function is called, there’s the following line:
The intention here was that everything except the leading zeros has already been copied. |
||
for (size_t i = 0; i < rem_leading_zeros; i++) { | ||
(*rem)->n_value[i] = 0; | ||
} | ||
_bc_rm_leading_zeros(*rem); | ||
if (bc_is_zero(*rem)) { | ||
(*rem)->n_sign = PLUS; | ||
(*rem)->n_scale = 0; | ||
} | ||
} | ||
} | ||
|
||
bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) | ||
bool bc_divide_ex(bc_num numerator, bc_num divisor, bc_num *quot, bc_num *rem, size_t scale, bool use_quot, bool use_rem) | ||
{ | ||
/* divide by zero */ | ||
if (bc_is_zero(divisor)) { | ||
return false; | ||
} | ||
|
||
bc_free_num(quot); | ||
size_t quot_scale = scale; | ||
size_t quot_scale = 0; | ||
size_t rem_scale = 0; | ||
if (use_quot) { | ||
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. Instead of using a separate argument for use_quot, use_rem. Why not check if quot/rem is a NULL pointer? |
||
bc_free_num(quot); | ||
} | ||
if (use_rem) { | ||
bc_free_num(rem); | ||
rem_scale = scale; | ||
} else { | ||
quot_scale = scale; | ||
} | ||
|
||
/* If numerator is zero, the quotient is always zero. */ | ||
if (bc_is_zero(numerator)) { | ||
|
@@ -349,8 +430,7 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) | |
|
||
/* 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) { | ||
bc_divide_by_one(numerator, quot, quot_scale); | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
bc_divide_by_one(numerator, divisor, quot, rem, quot_scale, rem_scale, use_quot, use_rem); | ||
return true; | ||
} | ||
|
||
|
@@ -372,10 +452,12 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) | |
numerator_size -= numerator_leading_zeros; | ||
|
||
/* check and remove divisor leading zeros */ | ||
size_t divisor_leading_zeros = 0; | ||
while (*divisorptr == 0) { | ||
divisorptr++; | ||
divisor_size--; | ||
divisor_leading_zeros++; | ||
} | ||
divisor_size -= divisor_leading_zeros; | ||
|
||
if (divisor_size > numerator_size) { | ||
goto quot_zero; | ||
|
@@ -393,39 +475,102 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale) | |
numerator_size -= divisor_trailing_zeros; | ||
|
||
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 (use_quot) { | ||
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 */ | ||
} | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
} | ||
|
||
/** | ||
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. I'm missing the high level explanation here on how you compute the remainder length |
||
* If the calculation uses more digits than the scale of rem, writing the vector directly to rem | ||
* will exceed the size, so calculate the excess size in advance. | ||
*/ | ||
size_t rem_over_size = 0; | ||
|
||
/** | ||
* Conversely, there are cases where the vector does not fill the rem size. | ||
* In this case, the size to be written is calculated in advance to determine the start position for writing to rem. | ||
*/ | ||
size_t rem_write_size = 0; | ||
|
||
size_t rem_size = 0; | ||
size_t numerator_rem_len_diff = 0; | ||
if (use_rem) { | ||
size_t divisor_int_size = divisor->n_len > divisor_leading_zeros ? divisor->n_len - divisor_leading_zeros : 0; | ||
size_t divisor_frac_size = divisor->n_scale > divisor_trailing_zeros ? divisor->n_scale - divisor_trailing_zeros : 0; | ||
rem_scale = MIN(MAX(numerator->n_scale, divisor_frac_size), rem_scale); | ||
|
||
*rem = bc_new_num_nonzeroed(divisor_int_size > 0 ? divisor_int_size : 1, rem_scale); // 1 is for 0 | ||
(*rem)->n_sign = numerator->n_sign; | ||
|
||
if (divisor_frac_size > rem_scale) { | ||
rem_over_size = divisor_frac_size - rem_scale; | ||
rem_write_size = (*rem)->n_len + rem_scale; | ||
} else { | ||
if (divisor_frac_size > 0) { | ||
rem_write_size = (*rem)->n_len + divisor_frac_size; | ||
} else { | ||
/* e.g. 123 % 30 */ | ||
rem_write_size = (*rem)->n_len - (divisor_trailing_zeros - divisor->n_scale); | ||
} | ||
} | ||
|
||
rem_size = (*rem)->n_len + (*rem)->n_scale; | ||
if (rem_size > rem_write_size) { | ||
size_t copy_size = rem_size - rem_write_size; | ||
numerator_rem_len_diff = numerator->n_len - (*rem)->n_len; | ||
memcpy((*rem)->n_value + rem_write_size, numerator->n_value + rem_write_size + numerator_rem_len_diff, copy_size); | ||
} | ||
} | ||
|
||
/* Size that can be read from numeratorptr */ | ||
size_t numerator_readable_size = numerator->n_len + numerator->n_scale - numerator_leading_zeros; | ||
|
||
/* 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; | ||
bc_divide_by_pow_10( | ||
numeratorptr, numerator_readable_size, numerator_leading_zeros, | ||
quot, quot_size, quot_scale, use_quot, | ||
rem, rem_size, use_rem, numerator_rem_len_diff | ||
); | ||
return true; | ||
} | ||
|
||
/* do divide */ | ||
bc_do_div( | ||
numeratorptr, numerator_size, numerator_readable_size, | ||
divisorptr, divisor_size, | ||
quot, quot_size | ||
quot, quot_size, | ||
rem, rem_over_size, rem_write_size, | ||
use_quot, use_rem | ||
); | ||
|
||
_bc_rm_leading_zeros(*quot); | ||
if (bc_is_zero(*quot)) { | ||
(*quot)->n_sign = PLUS; | ||
(*quot)->n_scale = 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. You no longer reset the scale in this case, but you only removed the leading zeros (on line 425) and not the trailing zeros. Can you explain please? |
||
} else { | ||
(*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS; | ||
if (use_quot) { | ||
_bc_rm_leading_zeros(*quot); | ||
if (bc_is_zero(*quot)) { | ||
(*quot)->n_sign = PLUS; | ||
(*quot)->n_scale = 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. I guess my previous question is answered because this line returned, but you should instead amend the previous commit so that it wasn't deleted in the first place. That keeps the history cleaner. 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. Sorry, I think a line was accidentally removed when I rebased and merged in master. I had only reviewed the final version of the code, so I didn’t notice that one of the intermediate commits included an unintended deletion. |
||
} | ||
} | ||
if (use_rem) { | ||
_bc_rm_leading_zeros(*rem); | ||
if (bc_is_zero(*rem)) { | ||
(*rem)->n_sign = PLUS; | ||
(*rem)->n_scale = 0; | ||
} | ||
} | ||
return true; | ||
|
||
quot_zero: | ||
*quot = bc_copy_num(BCG(_zero_)); | ||
if (use_quot) { | ||
*quot = bc_copy_num(BCG(_zero_)); | ||
} | ||
if (use_rem) { | ||
bc_divide_copy_numerator(numerator, rem, rem_scale); | ||
(*rem)->n_sign = numerator->n_sign; | ||
} | ||
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.
This function either needs a better name or a doc block explaining what it does on a high level. What is skip and why is it necessary? I don't know at this point.
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.
@nielsdos
For example, if
BC_VECTOR = [7890, 3456, 12]
, and want to construct achar
array like1234567
, need to skip three digits890
in7890
.This kind of skipping is necessary when storing the
rem
(from division) into achar
array, especially when the number of digits inBC_VECTOR
doesn’t match the requiredscale
for therem
.↑ Would this explanation be clear enough to convey the intent?