Skip to content

Commit 75f32b8

Browse files
committed
create fast path and move old logic to standard path
1 parent 22b6adb commit 75f32b8

File tree

1 file changed

+114
-24
lines changed
  • ext/bcmath/libbcmath/src

1 file changed

+114
-24
lines changed

ext/bcmath/libbcmath/src/sqrt.c

Lines changed: 114 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,37 +32,79 @@
3232
#include "bcmath.h"
3333
#include <stdbool.h>
3434
#include <stddef.h>
35+
#include "private.h"
3536

3637
/* Take the square root NUM and return it in NUM with SCALE digits
3738
after the decimal place. */
3839

39-
bool bc_sqrt(bc_num *num, size_t scale)
40+
static inline BC_VECTOR bc_sqrt_get_pow_10(size_t exponent)
4041
{
41-
/* Initial checks. */
42-
if (bc_is_neg(*num)) {
43-
/* Cannot take the square root of a negative number */
44-
return false;
42+
BC_VECTOR value = 1;
43+
while (exponent >= 8) {
44+
value *= BC_POW_10_LUT[8];
45+
exponent -= 8;
4546
}
46-
/* Square root of 0 is 0 */
47-
if (bc_is_zero(*num)) {
48-
bc_free_num (num);
49-
*num = bc_copy_num(BCG(_zero_));
50-
return true;
47+
value *= BC_POW_10_LUT[exponent];
48+
return value;
49+
}
50+
51+
static BC_VECTOR bc_fast_sqrt_vector(BC_VECTOR n_vector)
52+
{
53+
/* Use sqrt() from <math.h> to determine the initial value. */
54+
BC_VECTOR guess_vector = (BC_VECTOR) round(sqrt((double) n_vector));
55+
56+
/* Newton's algorithm. Iterative expression is `x_{n+1} = (x_n + a / x_n) / 2` */
57+
BC_VECTOR guess1_vector;
58+
size_t diff;
59+
do {
60+
guess1_vector = guess_vector;
61+
guess_vector = (guess1_vector + n_vector / guess1_vector) / 2; /* Iterative expression */
62+
diff = guess1_vector > guess_vector ? guess1_vector - guess_vector : guess_vector - guess1_vector;
63+
} while (diff > 1);
64+
return guess_vector;
65+
}
66+
67+
static inline void bc_fast_sqrt(bc_num *num, size_t scale, size_t num_calc_full_len, size_t num_use_full_len, size_t leading_zeros)
68+
{
69+
BC_VECTOR n_vector = 0;
70+
const char *nptr = (*num)->n_value + leading_zeros;
71+
for (size_t i = 0; i < num_use_full_len; i++) {
72+
n_vector = n_vector * BASE + *nptr++;
73+
}
74+
/* When calculating the square root of a number using only integer operations,
75+
* need to adjust the digit scale accordingly.
76+
* Considering that the original number is the square of the result,
77+
* if the desired scale of the result is 5, the input number should be scaled
78+
* by twice that, i.e., scale 10. */
79+
n_vector *= bc_sqrt_get_pow_10(num_calc_full_len - num_use_full_len);
80+
81+
/* Get sqrt */
82+
BC_VECTOR guess_vector = bc_fast_sqrt_vector(n_vector);
83+
84+
size_t ret_len;
85+
if (leading_zeros > 0) {
86+
ret_len = 1;
87+
} else {
88+
ret_len = ((*num)->n_len + 1) / 2;
5189
}
5290

53-
bcmath_compare_result num_cmp_one = bc_compare(*num, BCG(_one_), (*num)->n_scale);
54-
/* Square root of 1 is 1 */
55-
if (num_cmp_one == BCMATH_EQUAL) {
56-
bc_free_num (num);
57-
*num = bc_copy_num(BCG(_one_));
58-
return true;
91+
bc_num ret = bc_new_num_nonzeroed(ret_len, scale);
92+
char *rptr = ret->n_value;
93+
char *rend = rptr + ret_len + scale - 1;
94+
95+
guess_vector /= BASE; /* Since the scale of guess_vector is scale + 1, reduce the scale by 1. */
96+
while (rend >= rptr) {
97+
*rend-- = guess_vector % BASE;
98+
guess_vector /= BASE;
5999
}
100+
bc_free_num(num);
101+
*num = ret;
102+
}
60103

61-
/* Initialize the variables. */
62-
size_t cscale;
104+
static inline void bc_standard_sqrt(bc_num *num, size_t scale, bcmath_compare_result num_cmp_one)
105+
{
63106
bc_num guess;
64-
size_t rscale = MAX(scale, (*num)->n_scale);
65-
107+
size_t cscale;
66108
/* Calculate the initial guess. */
67109
if (num_cmp_one == BCMATH_RIGHT_GREATER) {
68110
/* The number is between 0 and 1. Guess should start at 1. */
@@ -86,7 +128,6 @@ bool bc_sqrt(bc_num *num, size_t scale)
86128
point5->n_value[1] = 5;
87129
bc_num diff = NULL;
88130

89-
/* Find the square root using Newton's algorithm. */
90131
bool done = false;
91132
while (!done) {
92133
bc_free_num (&guess1);
@@ -96,8 +137,8 @@ bool bc_sqrt(bc_num *num, size_t scale)
96137
bc_multiply_ex(guess, point5, &guess, cscale);
97138
bc_sub_ex(guess, guess1, &diff, cscale + 1);
98139
if (bc_is_near_zero(diff, cscale)) {
99-
if (cscale < rscale + 1) {
100-
cscale = MIN (cscale * 3, rscale + 1);
140+
if (cscale < scale + 1) {
141+
cscale = MIN (cscale * 3, scale + 1);
101142
} else {
102143
done = true;
103144
}
@@ -106,10 +147,59 @@ bool bc_sqrt(bc_num *num, size_t scale)
106147

107148
/* Assign the number and clean up. */
108149
bc_free_num (num);
109-
bc_divide(guess, BCG(_one_), num, rscale);
150+
bc_divide(guess, BCG(_one_), num, scale);
110151
bc_free_num (&guess);
111152
bc_free_num (&guess1);
112153
bc_free_num (&point5);
113154
bc_free_num (&diff);
155+
}
156+
157+
bool bc_sqrt(bc_num *num, size_t scale)
158+
{
159+
/* Initial checks. */
160+
if (bc_is_neg(*num)) {
161+
/* Cannot take the square root of a negative number */
162+
return false;
163+
}
164+
165+
size_t num_calc_scale = (scale + 1) * 2;
166+
size_t num_use_scale = MIN((*num)->n_scale, num_calc_scale);
167+
168+
/* Square root of 0 is 0 */
169+
if (bc_is_zero_for_scale(*num, num_use_scale)) {
170+
bc_free_num (num);
171+
*num = bc_copy_num(BCG(_zero_));
172+
return true;
173+
}
174+
175+
bcmath_compare_result num_cmp_one = bc_compare(*num, BCG(_one_), num_use_scale);
176+
/* Square root of 1 is 1 */
177+
if (num_cmp_one == BCMATH_EQUAL) {
178+
bc_free_num (num);
179+
*num = bc_copy_num(BCG(_one_));
180+
return true;
181+
}
182+
183+
/* Initialize the variables. */
184+
size_t leading_zeros = 0;
185+
size_t num_calc_full_len = (*num)->n_len + num_calc_scale;
186+
size_t num_use_full_len = (*num)->n_len + num_use_scale;
187+
if (num_cmp_one == BCMATH_RIGHT_GREATER) {
188+
const char *nptr = (*num)->n_value;
189+
while (*nptr == 0) {
190+
leading_zeros++;
191+
nptr++;
192+
}
193+
num_calc_full_len -= leading_zeros;
194+
num_use_full_len -= leading_zeros;
195+
}
196+
197+
/* Find the square root using Newton's algorithm. */
198+
if (num_calc_full_len < MAX_LENGTH_OF_LONG) {
199+
bc_fast_sqrt(num, scale, num_calc_full_len, num_use_full_len, leading_zeros);
200+
} else {
201+
bc_standard_sqrt(num, scale, num_cmp_one);
202+
}
203+
114204
return true;
115205
}

0 commit comments

Comments
 (0)