Skip to content

Commit 69c5b61

Browse files
committed
create fast path
1 parent 3600642 commit 69c5b61

File tree

1 file changed

+121
-42
lines changed
  • ext/bcmath/libbcmath/src

1 file changed

+121
-42
lines changed

ext/bcmath/libbcmath/src/sqrt.c

Lines changed: 121 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,47 @@
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

40+
static inline BC_VECTOR bc_sqrt_get_pow_10(size_t exponent)
41+
{
42+
BC_VECTOR value = 1;
43+
while (exponent >= 8) {
44+
value *= BC_POW_10_LUT[8];
45+
exponent -= 8;
46+
}
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 a bitwise method for approximating the square root
54+
* as the initial guess for Newton's method. */
55+
union {
56+
uint64_t i;
57+
double d;
58+
} u;
59+
u.d = (double) n_vector;
60+
u.i = (1ULL << 61) + (u.i >> 1) - (1ULL << 50);
61+
BC_VECTOR guess_vector = (BC_VECTOR) u.d;
62+
63+
/* Newton's algorithm. */
64+
BC_VECTOR guess1_vector;
65+
size_t diff;
66+
do {
67+
guess1_vector = guess_vector;
68+
guess_vector = n_vector / guess_vector;
69+
guess_vector += guess1_vector;
70+
guess_vector /= 2;
71+
diff = guess1_vector > guess_vector ? guess1_vector - guess_vector : guess_vector - guess1_vector;
72+
} while (diff > 1);
73+
return guess_vector;
74+
}
75+
3976
bool bc_sqrt(bc_num *num, size_t scale)
4077
{
4178
/* Initial checks. */
@@ -59,55 +96,97 @@ bool bc_sqrt(bc_num *num, size_t scale)
5996
}
6097

6198
/* Initialize the variables. */
62-
size_t cscale;
63-
bc_num guess;
6499
size_t rscale = MAX(scale, (*num)->n_scale);
65100

66-
/* Calculate the initial guess. */
67-
if (num_cmp_one == BCMATH_RIGHT_GREATER) {
68-
/* The number is between 0 and 1. Guess should start at 1. */
69-
guess = bc_copy_num(BCG(_one_));
70-
cscale = (*num)->n_scale;
71-
} else {
72-
/* The number is greater than 1. Guess should start at 10^(exp/2). */
73-
/* If just divide size_t by 2 it will not overflow. */
74-
size_t exponent_for_initial_guess = (size_t) (*num)->n_len >> 1;
75-
76-
/* 10^n is a 1 followed by n zeros. */
77-
guess = bc_new_num(exponent_for_initial_guess + 1, 0);
78-
guess->n_value[0] = 1;
79-
cscale = 3;
80-
}
101+
/* Find the square root using Newton's algorithm. */
102+
if ((*num)->n_len + (rscale + 1) * 2 < MAX_LENGTH_OF_LONG) {
103+
/* Fast path */
81104

82-
bc_num guess1 = NULL;
83-
bc_num point5 = bc_new_num (1, 1);
84-
point5->n_value[1] = 5;
85-
bc_num diff = NULL;
105+
BC_VECTOR n_vector = 0;
106+
size_t i = 0;
107+
for (; i < (*num)->n_len + (*num)->n_scale; i++) {
108+
n_vector = n_vector * BASE + (*num)->n_value[i];
109+
}
110+
/* When calculating the square root of a number using only integer operations,
111+
* need to adjust the digit scale accordingly.
112+
* Considering that the original number is the square of the result,
113+
* if the desired scale of the result is 5, the input number should be scaled
114+
* by twice that, i.e., scale 10. */
115+
n_vector *= bc_sqrt_get_pow_10((rscale + 1) * 2 - (*num)->n_scale);
116+
117+
/* Get sqrt */
118+
BC_VECTOR guess_vector = bc_fast_sqrt_vector(n_vector);
119+
120+
size_t full_len = 0;
121+
BC_VECTOR tmp_guess_vector = guess_vector;
122+
do {
123+
tmp_guess_vector /= BASE;
124+
full_len++;
125+
} while (tmp_guess_vector > 0);
126+
127+
size_t ret_ren = full_len > rscale + 1 ? full_len - (rscale + 1) : 1; /* for int zero */
128+
bc_num ret = bc_new_num_nonzeroed(ret_ren, rscale);
129+
char *rptr = ret->n_value;
130+
char *rend = rptr + ret_ren + rscale - 1;
131+
132+
guess_vector /= BASE; /* Since the scale of guess_vector is rscale + 1, reduce the scale by 1. */
133+
while (rend >= rptr) {
134+
*rend-- = guess_vector % BASE;
135+
guess_vector /= BASE;
136+
}
137+
bc_free_num(num);
138+
*num = ret;
139+
} else {
140+
/* Standard path */
141+
142+
bc_num guess;
143+
size_t cscale;
144+
/* Calculate the initial guess. */
145+
if (num_cmp_one == BCMATH_RIGHT_GREATER) {
146+
/* The number is between 0 and 1. Guess should start at 1. */
147+
guess = bc_copy_num(BCG(_one_));
148+
cscale = (*num)->n_scale;
149+
} else {
150+
/* The number is greater than 1. Guess should start at 10^(exp/2). */
151+
/* If just divide size_t by 2 it will not overflow. */
152+
size_t exponent_for_initial_guess = (size_t) (*num)->n_len >> 1;
153+
154+
/* 10^n is a 1 followed by n zeros. */
155+
guess = bc_new_num(exponent_for_initial_guess + 1, 0);
156+
guess->n_value[0] = 1;
157+
cscale = 3;
158+
}
86159

87-
/* Find the square root using Newton's algorithm. */
88-
bool done = false;
89-
while (!done) {
90-
bc_free_num (&guess1);
91-
guess1 = bc_copy_num(guess);
92-
bc_divide(*num, guess, &guess, cscale);
93-
bc_add_ex(guess, guess1, &guess, 0);
94-
bc_multiply_ex(guess, point5, &guess, cscale);
95-
bc_sub_ex(guess, guess1, &diff, cscale + 1);
96-
if (bc_is_near_zero(diff, cscale)) {
97-
if (cscale < rscale + 1) {
98-
cscale = MIN (cscale * 3, rscale + 1);
99-
} else {
100-
done = true;
160+
bc_num guess1 = NULL;
161+
bc_num point5 = bc_new_num (1, 1);
162+
point5->n_value[1] = 5;
163+
bc_num diff = NULL;
164+
165+
bool done = false;
166+
while (!done) {
167+
bc_free_num (&guess1);
168+
guess1 = bc_copy_num(guess);
169+
bc_divide(*num, guess, &guess, cscale);
170+
bc_add_ex(guess, guess1, &guess, 0);
171+
bc_multiply_ex(guess, point5, &guess, cscale);
172+
bc_sub_ex(guess, guess1, &diff, cscale + 1);
173+
if (bc_is_near_zero(diff, cscale)) {
174+
if (cscale < rscale + 1) {
175+
cscale = MIN (cscale * 3, rscale + 1);
176+
} else {
177+
done = true;
178+
}
101179
}
102180
}
181+
182+
/* Assign the number and clean up. */
183+
bc_free_num (num);
184+
bc_divide(guess, BCG(_one_), num, rscale);
185+
bc_free_num (&guess);
186+
bc_free_num (&guess1);
187+
bc_free_num (&point5);
188+
bc_free_num (&diff);
103189
}
104190

105-
/* Assign the number and clean up. */
106-
bc_free_num (num);
107-
bc_divide(guess, BCG(_one_), num, rscale);
108-
bc_free_num (&guess);
109-
bc_free_num (&guess1);
110-
bc_free_num (&point5);
111-
bc_free_num (&diff);
112191
return true;
113192
}

0 commit comments

Comments
 (0)