Skip to content

Commit d055b51

Browse files
committed
Rewrite BigDecimal#sqrt in ruby with improved Newton's method
1 parent 73c1caf commit d055b51

File tree

4 files changed

+58
-258
lines changed

4 files changed

+58
-258
lines changed

ext/bigdecimal/bigdecimal.c

Lines changed: 2 additions & 249 deletions
Original file line numberDiff line numberDiff line change
@@ -196,19 +196,6 @@ rbd_allocate_struct_zero(int sign, size_t const digits)
196196
return real;
197197
}
198198

199-
// Only used in VpSqrt through BigDecimal_sqrt
200-
MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits));
201-
#define NewOneNolimit rbd_allocate_struct_one_nolimit
202-
static inline Real *
203-
rbd_allocate_struct_one_nolimit(int sign, size_t const digits)
204-
{
205-
Real *real = rbd_allocate_struct_decimal_digits(digits);
206-
VpSetOne(real);
207-
if (sign < 0)
208-
VpSetSign(real, VP_SIGN_NEGATIVE_FINITE);
209-
return real;
210-
}
211-
212199
/*
213200
* ================== Ruby Interface part ==========================
214201
*/
@@ -281,20 +268,6 @@ rbd_allocate_struct_zero_wrap(int sign, size_t const digits)
281268
return (BDVALUE) { BigDecimal_wrap_struct(rb_cBigDecimal, real), real };
282269
}
283270

284-
// Only used in BigDecimal_sqrt
285-
MAYBE_UNUSED(static inline BDVALUE rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits));
286-
#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap
287-
static inline BDVALUE
288-
rbd_allocate_struct_zero_limited_wrap(int sign, size_t digits)
289-
{
290-
size_t prec_limit = VpGetPrecLimit();
291-
if (prec_limit) {
292-
prec_limit += 2 * BASE_FIG; /* 2 more digits for rounding and division */
293-
if (prec_limit < digits) digits = prec_limit;
294-
}
295-
return rbd_allocate_struct_zero_wrap(sign, digits);
296-
}
297-
298271
static inline int
299272
is_kind_of_BigDecimal(VALUE const v)
300273
{
@@ -2090,32 +2063,6 @@ BigDecimal_abs(VALUE self)
20902063
return CheckGetValue(c);
20912064
}
20922065

2093-
/* call-seq:
2094-
* sqrt(n)
2095-
*
2096-
* Returns the square root of the value.
2097-
*
2098-
* Result has at least n significant digits.
2099-
*/
2100-
static VALUE
2101-
BigDecimal_sqrt(VALUE self, VALUE nFig)
2102-
{
2103-
BDVALUE c, a;
2104-
size_t mx, n;
2105-
2106-
a = GetBDValueMust(self);
2107-
mx = a.real->Prec * (VpBaseFig() + 1);
2108-
2109-
n = check_int_precision(nFig);
2110-
n += VpDblFig() + VpBaseFig();
2111-
if (mx <= n) mx = n;
2112-
c = NewZeroWrapLimited(1, mx);
2113-
VpSqrt(c.real, a.real);
2114-
2115-
RB_GC_GUARD(a.bigdecimal);
2116-
return CheckGetValue(c);
2117-
}
2118-
21192066
/* Return the integer part of the number, as a BigDecimal.
21202067
*/
21212068
static VALUE
@@ -3603,7 +3550,6 @@ Init_bigdecimal(void)
36033550
rb_define_method(rb_cBigDecimal, "dup", BigDecimal_clone, 0);
36043551
rb_define_method(rb_cBigDecimal, "to_f", BigDecimal_to_f, 0);
36053552
rb_define_method(rb_cBigDecimal, "abs", BigDecimal_abs, 0);
3606-
rb_define_method(rb_cBigDecimal, "sqrt", BigDecimal_sqrt, 1);
36073553
rb_define_method(rb_cBigDecimal, "fix", BigDecimal_fix, 0);
36083554
rb_define_method(rb_cBigDecimal, "round", BigDecimal_round, -1);
36093555
rb_define_method(rb_cBigDecimal, "frac", BigDecimal_frac, 0);
@@ -3675,9 +3621,6 @@ static int gfDebug = 1; /* Debug switch */
36753621
#endif /* BIGDECIMAL_DEBUG */
36763622

36773623
static Real *VpConstOne; /* constant 1.0 */
3678-
static Real *VpConstPt5; /* constant 0.5 */
3679-
#define maxnr 100UL /* Maximum iterations for calculating sqrt. */
3680-
/* used in VpSqrt() */
36813624

36823625
enum op_sw {
36833626
OP_SW_ADD = 1, /* + */
@@ -4082,12 +4025,6 @@ VpInit(DECDIG BaseVal)
40824025
VpConstOne = NewZero(1, 1);
40834026
VpSetOne(VpConstOne);
40844027

4085-
/* Const 0.5 */
4086-
VpConstPt5 = NewZero(1, 1);
4087-
VpSetSign(VpConstPt5, 1);
4088-
VpConstPt5->exponent = 0;
4089-
VpConstPt5->frac[0] = 5*BASE1;
4090-
40914028
#ifdef BIGDECIMAL_DEBUG
40924029
gnAlloc = 0;
40934030
#endif /* BIGDECIMAL_DEBUG */
@@ -4892,7 +4829,6 @@ VpMult(Real *c, Real *a, Real *b)
48924829
size_t ind_as, ind_ae, ind_bs;
48934830
DECDIG carry;
48944831
DECDIG_DBL s;
4895-
Real *w;
48964832

48974833
if (!VpIsDefOP(c, a, b, OP_SW_MULT)) return 0; /* No significant digit */
48984834

@@ -4912,29 +4848,19 @@ VpMult(Real *c, Real *a, Real *b)
49124848
}
49134849
if (b->Prec > a->Prec) {
49144850
/* Adjust so that digits(a)>digits(b) */
4915-
w = a;
4851+
Real *w = a;
49164852
a = b;
49174853
b = w;
49184854
}
4919-
w = NULL;
49204855
MxIndA = a->Prec - 1;
49214856
MxIndB = b->Prec - 1;
49224857
MxIndAB = a->Prec + b->Prec - 1;
49234858

4924-
// Only VpSqrt calls VpMult with insufficient precision
4925-
if (c->MaxPrec < VPMULT_RESULT_PREC(a, b)) {
4926-
w = c;
4927-
c = NewZero(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG);
4928-
}
4929-
49304859
/* set LHSV c info */
49314860

49324861
c->exponent = a->exponent; /* set exponent */
49334862
VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */
4934-
if (!AddExponent(c, b->exponent)) {
4935-
if (w) rbd_free_struct(c);
4936-
return 0;
4937-
}
4863+
if (!AddExponent(c, b->exponent)) return 0;
49384864
carry = 0;
49394865
nc = ind_c = MxIndAB;
49404866
memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */
@@ -4982,11 +4908,6 @@ VpMult(Real *c, Real *a, Real *b)
49824908
}
49834909
}
49844910
VpNmlz(c);
4985-
if (w != NULL) { /* free work variable */
4986-
VpAsgn(w, c, 10);
4987-
rbd_free_struct(c);
4988-
c = w;
4989-
}
49904911

49914912
Exit:
49924913
return c->Prec*BASE_FIG;
@@ -5888,174 +5809,6 @@ VpVtoD(double *d, SIGNED_VALUE *e, Real *m)
58885809
return f;
58895810
}
58905811

5891-
/*
5892-
* m <- d
5893-
*/
5894-
VP_EXPORT void
5895-
VpDtoV(Real *m, double d)
5896-
{
5897-
size_t ind_m, mm;
5898-
SIGNED_VALUE ne;
5899-
DECDIG i;
5900-
double val, val2;
5901-
5902-
if (isnan(d)) {
5903-
VpSetNaN(m);
5904-
goto Exit;
5905-
}
5906-
if (isinf(d)) {
5907-
if (d > 0.0) VpSetPosInf(m);
5908-
else VpSetNegInf(m);
5909-
goto Exit;
5910-
}
5911-
5912-
if (d == 0.0) {
5913-
VpSetZero(m, 1);
5914-
goto Exit;
5915-
}
5916-
val = (d > 0.) ? d : -d;
5917-
ne = 0;
5918-
if (val >= 1.0) {
5919-
while (val >= 1.0) {
5920-
val /= (double)BASE;
5921-
++ne;
5922-
}
5923-
}
5924-
else {
5925-
val2 = 1.0 / (double)BASE;
5926-
while (val < val2) {
5927-
val *= (double)BASE;
5928-
--ne;
5929-
}
5930-
}
5931-
/* Now val = 0.xxxxx*BASE**ne */
5932-
5933-
mm = m->MaxPrec;
5934-
memset(m->frac, 0, mm * sizeof(DECDIG));
5935-
for (ind_m = 0; val > 0.0 && ind_m < mm; ind_m++) {
5936-
val *= (double)BASE;
5937-
i = (DECDIG)val;
5938-
val -= (double)i;
5939-
m->frac[ind_m] = i;
5940-
}
5941-
if (ind_m >= mm) ind_m = mm - 1;
5942-
VpSetSign(m, (d > 0.0) ? 1 : -1);
5943-
m->Prec = ind_m + 1;
5944-
m->exponent = ne;
5945-
5946-
VpInternalRound(m, 0, (m->Prec > 0) ? m->frac[m->Prec-1] : 0,
5947-
(DECDIG)(val*(double)BASE));
5948-
5949-
Exit:
5950-
return;
5951-
}
5952-
5953-
/*
5954-
* y = SQRT(x), y*y - x =>0
5955-
*/
5956-
VP_EXPORT int
5957-
VpSqrt(Real *y, Real *x)
5958-
{
5959-
Real *f = NULL;
5960-
Real *r = NULL;
5961-
size_t y_prec;
5962-
SIGNED_VALUE n, e;
5963-
ssize_t nr;
5964-
double val;
5965-
5966-
/* Zero or +Infinity ? */
5967-
if (VpIsZero(x) || VpIsPosInf(x)) {
5968-
VpAsgn(y,x,1);
5969-
goto Exit;
5970-
}
5971-
5972-
/* Negative ? */
5973-
if (BIGDECIMAL_NEGATIVE_P(x)) {
5974-
VpSetNaN(y);
5975-
return VpException(VP_EXCEPTION_OP, "sqrt of negative value", 0);
5976-
}
5977-
5978-
/* NaN ? */
5979-
if (VpIsNaN(x)) {
5980-
VpSetNaN(y);
5981-
return VpException(VP_EXCEPTION_OP, "sqrt of 'NaN'(Not a Number)", 0);
5982-
}
5983-
5984-
/* One ? */
5985-
if (VpIsOne(x)) {
5986-
VpSetOne(y);
5987-
goto Exit;
5988-
}
5989-
5990-
n = (SIGNED_VALUE)y->MaxPrec;
5991-
if (x->MaxPrec > (size_t)n) n = (ssize_t)x->MaxPrec;
5992-
5993-
/* allocate temporally variables */
5994-
/* TODO: reconsider MaxPrec of f and r */
5995-
f = NewOneNolimit(1, y->MaxPrec * (BASE_FIG + 2));
5996-
r = NewOneNolimit(1, (n + n) * (BASE_FIG + 2));
5997-
5998-
nr = 0;
5999-
y_prec = y->MaxPrec;
6000-
6001-
VpVtoD(&val, &e, x); /* val <- x */
6002-
e /= (SIGNED_VALUE)BASE_FIG;
6003-
n = e / 2;
6004-
if (e - n * 2 != 0) {
6005-
val /= BASE;
6006-
n = (e + 1) / 2;
6007-
}
6008-
VpDtoV(y, sqrt(val)); /* y <- sqrt(val) */
6009-
y->exponent += n;
6010-
n = (SIGNED_VALUE)roomof(BIGDECIMAL_DOUBLE_FIGURES, BASE_FIG);
6011-
y->MaxPrec = Min((size_t)n , y_prec);
6012-
f->MaxPrec = y->MaxPrec + 1;
6013-
n = (SIGNED_VALUE)(y_prec * BASE_FIG);
6014-
if (n > (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr;
6015-
6016-
/*
6017-
* Perform: y_{n+1} = (y_n - x/y_n) / 2
6018-
*/
6019-
do {
6020-
y->MaxPrec *= 2;
6021-
if (y->MaxPrec > y_prec) y->MaxPrec = y_prec;
6022-
f->MaxPrec = y->MaxPrec;
6023-
VpDivd(f, r, x, y); /* f = x/y */
6024-
VpAddSub(r, f, y, -1); /* r = f - y */
6025-
VpMult(f, VpConstPt5, r); /* f = 0.5*r */
6026-
if (y_prec == y->MaxPrec && VpIsZero(f))
6027-
goto converge;
6028-
VpAddSub(r, f, y, 1); /* r = y + f */
6029-
VpAsgn(y, r, 1); /* y = r */
6030-
} while (++nr < n);
6031-
6032-
#ifdef BIGDECIMAL_DEBUG
6033-
if (gfDebug) {
6034-
printf("ERROR(VpSqrt): did not converge within %ld iterations.\n", nr);
6035-
}
6036-
#endif /* BIGDECIMAL_DEBUG */
6037-
y->MaxPrec = y_prec;
6038-
6039-
converge:
6040-
VpChangeSign(y, 1);
6041-
#ifdef BIGDECIMAL_DEBUG
6042-
if (gfDebug) {
6043-
VpMult(r, y, y);
6044-
VpAddSub(f, x, r, -1);
6045-
printf("VpSqrt: iterations = %"PRIdSIZE"\n", nr);
6046-
VPrint(stdout, " y =% \n", y);
6047-
VPrint(stdout, " x =% \n", x);
6048-
VPrint(stdout, " x-y*y = % \n", f);
6049-
}
6050-
#endif /* BIGDECIMAL_DEBUG */
6051-
y->MaxPrec = y_prec;
6052-
6053-
Exit:
6054-
rbd_free_struct(f);
6055-
rbd_free_struct(r);
6056-
return 1;
6057-
}
6058-
60595812
/*
60605813
* Round relatively from the decimal point.
60615814
* f: rounding mode

ext/bigdecimal/bigdecimal.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ typedef struct {
195195
*/
196196

197197
#define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES
198-
#define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES
199198

200199
/* Zero,Inf,NaN (isinf(),isnan() used to check) */
201200
VP_EXPORT double VpGetDoubleNaN(void);
@@ -229,8 +228,6 @@ VP_EXPORT void VpToString(Real *a, char *buf, size_t bufsize, size_t fFmt, int f
229228
VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus);
230229
VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne);
231230
VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m);
232-
VP_EXPORT void VpDtoV(Real *m,double d);
233-
VP_EXPORT int VpSqrt(Real *y,Real *x);
234231
VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il);
235232
VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf);
236233
VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf);

0 commit comments

Comments
 (0)