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+
3976bool bc_sqrt (bc_num * num , size_t scale )
4077{
4178 /* Initial checks. */
@@ -59,55 +96,98 @@ 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 );
100+ size_t num_calc_full_len = (* num )-> n_len + (rscale + 1 ) * 2 ;
65101
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- }
102+ /* Find the square root using Newton's algorithm. */
103+ if (num_calc_full_len < MAX_LENGTH_OF_LONG ) {
104+ /* Fast path */
81105
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 ;
106+ BC_VECTOR n_vector = 0 ;
107+ size_t i = 0 ;
108+ for (; i < (* num )-> n_len + (* num )-> n_scale ; i ++ ) {
109+ n_vector = n_vector * BASE + (* num )-> n_value [i ];
110+ }
111+ /* When calculating the square root of a number using only integer operations,
112+ * need to adjust the digit scale accordingly.
113+ * Considering that the original number is the square of the result,
114+ * if the desired scale of the result is 5, the input number should be scaled
115+ * by twice that, i.e., scale 10. */
116+ n_vector *= bc_sqrt_get_pow_10 ((rscale + 1 ) * 2 - (* num )-> n_scale );
117+
118+ /* Get sqrt */
119+ BC_VECTOR guess_vector = bc_fast_sqrt_vector (n_vector );
120+
121+ size_t full_len = 0 ;
122+ BC_VECTOR tmp_guess_vector = guess_vector ;
123+ do {
124+ tmp_guess_vector /= BASE ;
125+ full_len ++ ;
126+ } while (tmp_guess_vector > 0 );
127+
128+ size_t ret_ren = full_len > rscale + 1 ? full_len - (rscale + 1 ) : 1 ; /* for int zero */
129+ bc_num ret = bc_new_num_nonzeroed (ret_ren , rscale );
130+ char * rptr = ret -> n_value ;
131+ char * rend = rptr + ret_ren + rscale - 1 ;
132+
133+ guess_vector /= BASE ; /* Since the scale of guess_vector is rscale + 1, reduce the scale by 1. */
134+ while (rend >= rptr ) {
135+ * rend -- = guess_vector % BASE ;
136+ guess_vector /= BASE ;
137+ }
138+ bc_free_num (num );
139+ * num = ret ;
140+ } else {
141+ /* Standard path */
142+
143+ bc_num guess ;
144+ size_t cscale ;
145+ /* Calculate the initial guess. */
146+ if (num_cmp_one == BCMATH_RIGHT_GREATER ) {
147+ /* The number is between 0 and 1. Guess should start at 1. */
148+ guess = bc_copy_num (BCG (_one_ ));
149+ cscale = (* num )-> n_scale ;
150+ } else {
151+ /* The number is greater than 1. Guess should start at 10^(exp/2). */
152+ /* If just divide size_t by 2 it will not overflow. */
153+ size_t exponent_for_initial_guess = (size_t ) (* num )-> n_len >> 1 ;
154+
155+ /* 10^n is a 1 followed by n zeros. */
156+ guess = bc_new_num (exponent_for_initial_guess + 1 , 0 );
157+ guess -> n_value [0 ] = 1 ;
158+ cscale = 3 ;
159+ }
86160
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;
161+ bc_num guess1 = NULL ;
162+ bc_num point5 = bc_new_num (1 , 1 );
163+ point5 -> n_value [1 ] = 5 ;
164+ bc_num diff = NULL ;
165+
166+ bool done = false;
167+ while (!done ) {
168+ bc_free_num (& guess1 );
169+ guess1 = bc_copy_num (guess );
170+ bc_divide (* num , guess , & guess , cscale );
171+ bc_add_ex (guess , guess1 , & guess , 0 );
172+ bc_multiply_ex (guess , point5 , & guess , cscale );
173+ bc_sub_ex (guess , guess1 , & diff , cscale + 1 );
174+ if (bc_is_near_zero (diff , cscale )) {
175+ if (cscale < rscale + 1 ) {
176+ cscale = MIN (cscale * 3 , rscale + 1 );
177+ } else {
178+ done = true;
179+ }
101180 }
102181 }
182+
183+ /* Assign the number and clean up. */
184+ bc_free_num (num );
185+ bc_divide (guess , BCG (_one_ ), num , rscale );
186+ bc_free_num (& guess );
187+ bc_free_num (& guess1 );
188+ bc_free_num (& point5 );
189+ bc_free_num (& diff );
103190 }
104191
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 );
112192 return true;
113193}
0 commit comments