Skip to content

Commit 3d8b9be

Browse files
authored
Rewrite BigDecimal#sqrt in ruby with improved Newton's method (#381)
* Rewrite BigDecimal#sqrt in ruby with improved Newton's method * Use common prec validation also in BigDecimal#sqrt * Use common EXCEPTION_INFINITY check in sqrt * Limit sqrt result when prec limit is set
1 parent 99cc2d5 commit 3d8b9be

File tree

4 files changed

+72
-266
lines changed

4 files changed

+72
-266
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
{
@@ -2081,32 +2054,6 @@ BigDecimal_abs(VALUE self)
20812054
return CheckGetValue(c);
20822055
}
20832056

2084-
/* call-seq:
2085-
* sqrt(n)
2086-
*
2087-
* Returns the square root of the value.
2088-
*
2089-
* Result has at least n significant digits.
2090-
*/
2091-
static VALUE
2092-
BigDecimal_sqrt(VALUE self, VALUE nFig)
2093-
{
2094-
BDVALUE c, a;
2095-
size_t mx, n;
2096-
2097-
a = GetBDValueMust(self);
2098-
mx = a.real->Prec * (VpBaseFig() + 1);
2099-
2100-
n = check_int_precision(nFig);
2101-
n += VpDblFig() + VpBaseFig();
2102-
if (mx <= n) mx = n;
2103-
c = NewZeroWrapLimited(1, mx);
2104-
VpSqrt(c.real, a.real);
2105-
2106-
RB_GC_GUARD(a.bigdecimal);
2107-
return CheckGetValue(c);
2108-
}
2109-
21102057
/* Return the integer part of the number, as a BigDecimal.
21112058
*/
21122059
static VALUE
@@ -3594,7 +3541,6 @@ Init_bigdecimal(void)
35943541
rb_define_method(rb_cBigDecimal, "dup", BigDecimal_clone, 0);
35953542
rb_define_method(rb_cBigDecimal, "to_f", BigDecimal_to_f, 0);
35963543
rb_define_method(rb_cBigDecimal, "abs", BigDecimal_abs, 0);
3597-
rb_define_method(rb_cBigDecimal, "sqrt", BigDecimal_sqrt, 1);
35983544
rb_define_method(rb_cBigDecimal, "fix", BigDecimal_fix, 0);
35993545
rb_define_method(rb_cBigDecimal, "round", BigDecimal_round, -1);
36003546
rb_define_method(rb_cBigDecimal, "frac", BigDecimal_frac, 0);
@@ -3666,9 +3612,6 @@ static int gfDebug = 1; /* Debug switch */
36663612
#endif /* BIGDECIMAL_DEBUG */
36673613

36683614
static Real *VpConstOne; /* constant 1.0 */
3669-
static Real *VpConstPt5; /* constant 0.5 */
3670-
#define maxnr 100UL /* Maximum iterations for calculating sqrt. */
3671-
/* used in VpSqrt() */
36723615

36733616
enum op_sw {
36743617
OP_SW_ADD = 1, /* + */
@@ -4073,12 +4016,6 @@ VpInit(DECDIG BaseVal)
40734016
VpConstOne = NewZero(1, 1);
40744017
VpSetOne(VpConstOne);
40754018

4076-
/* Const 0.5 */
4077-
VpConstPt5 = NewZero(1, 1);
4078-
VpSetSign(VpConstPt5, 1);
4079-
VpConstPt5->exponent = 0;
4080-
VpConstPt5->frac[0] = 5*BASE1;
4081-
40824019
#ifdef BIGDECIMAL_DEBUG
40834020
gnAlloc = 0;
40844021
#endif /* BIGDECIMAL_DEBUG */
@@ -4883,7 +4820,6 @@ VpMult(Real *c, Real *a, Real *b)
48834820
size_t ind_as, ind_ae, ind_bs;
48844821
DECDIG carry;
48854822
DECDIG_DBL s;
4886-
Real *w;
48874823

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

@@ -4903,29 +4839,19 @@ VpMult(Real *c, Real *a, Real *b)
49034839
}
49044840
if (b->Prec > a->Prec) {
49054841
/* Adjust so that digits(a)>digits(b) */
4906-
w = a;
4842+
Real *w = a;
49074843
a = b;
49084844
b = w;
49094845
}
4910-
w = NULL;
49114846
MxIndA = a->Prec - 1;
49124847
MxIndB = b->Prec - 1;
49134848
MxIndAB = a->Prec + b->Prec - 1;
49144849

4915-
// Only VpSqrt calls VpMult with insufficient precision
4916-
if (c->MaxPrec < VPMULT_RESULT_PREC(a, b)) {
4917-
w = c;
4918-
c = NewZero(1, VPMULT_RESULT_PREC(a, b) * BASE_FIG);
4919-
}
4920-
49214850
/* set LHSV c info */
49224851

49234852
c->exponent = a->exponent; /* set exponent */
49244853
VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */
4925-
if (!AddExponent(c, b->exponent)) {
4926-
if (w) rbd_free_struct(c);
4927-
return 0;
4928-
}
4854+
if (!AddExponent(c, b->exponent)) return 0;
49294855
carry = 0;
49304856
nc = ind_c = MxIndAB;
49314857
memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */
@@ -4973,11 +4899,6 @@ VpMult(Real *c, Real *a, Real *b)
49734899
}
49744900
}
49754901
VpNmlz(c);
4976-
if (w != NULL) { /* free work variable */
4977-
VpAsgn(w, c, 10);
4978-
rbd_free_struct(c);
4979-
c = w;
4980-
}
49814902

49824903
Exit:
49834904
return c->Prec*BASE_FIG;
@@ -5879,174 +5800,6 @@ VpVtoD(double *d, SIGNED_VALUE *e, Real *m)
58795800
return f;
58805801
}
58815802

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