Skip to content

Commit 4fbec55

Browse files
committed
Add BigDecimal#scale
Fixes GH-198.
1 parent 8fc83dd commit 4fbec55

File tree

2 files changed

+195
-59
lines changed

2 files changed

+195
-59
lines changed

ext/bigdecimal/bigdecimal.c

Lines changed: 142 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -319,110 +319,192 @@ BigDecimal_prec(VALUE self)
319319
return obj;
320320
}
321321

322-
/*
323-
* call-seq:
324-
* precision -> integer
325-
*
326-
* Returns the number of decimal digits in +self+:
327-
*
328-
* BigDecimal("0").precision # => 0
329-
* BigDecimal("1").precision # => 1
330-
* BigDecimal("-1e20").precision # => 21
331-
* BigDecimal("1e-20").precision # => 20
332-
* BigDecimal("Infinity").precision # => 0
333-
* BigDecimal("-Infinity").precision # => 0
334-
* BigDecimal("NaN").precision # => 0
335-
*
336-
*/
337-
static VALUE
338-
BigDecimal_precision(VALUE self)
322+
static void
323+
BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale)
339324
{
340325
ENTER(1);
341326

327+
if (out_precision == NULL && out_scale == NULL)
328+
return;
329+
342330
Real *p;
343331
GUARD_OBJ(p, GetVpValue(self, 1));
344-
if (VpIsZero(p) || !VpIsDef(p)) return INT2FIX(0);
332+
if (VpIsZero(p) || !VpIsDef(p)) {
333+
zero:
334+
if (out_precision) *out_precision = 0;
335+
if (out_scale) *out_scale = 0;
336+
return;
337+
}
338+
339+
DECDIG x;
340+
341+
ssize_t n = p->Prec; /* The length of frac without zeros. */
342+
while (n > 0 && p->frac[n-1] == 0) --n;
343+
if (n == 0) goto zero;
344+
345+
int nlz = BASE_FIG;
346+
for (x = p->frac[0]; x > 0; x /= 10) --nlz;
347+
348+
int ntz = 0;
349+
for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz;
345350

346351
/*
347-
* The most significant digit is frac[0], and the least significant digit is frac[Prec-1].
348-
* When the exponent is zero, the decimal point is located just before frac[0].
352+
* Calculate the precision and the scale
353+
* -------------------------------------
349354
*
355+
* The most significant digit is frac[0], and the least significant digit
356+
* is frac[Prec-1]. When the exponent is zero, the decimal point is
357+
* located just before frac[0].
350358
*
351359
* When the exponent is negative, the decimal point moves to leftward.
352-
* In this case, the precision can be calculated by BASE_FIG * (-exponent + Prec) - ntz.
360+
* In this case, the precision can be calculated by
361+
*
362+
* precision = BASE_FIG * (-exponent + n) - ntz,
363+
*
364+
* and the scale is the same as precision.
353365
*
354-
* 0 . 0000 0000 | frac[0] frac[1] ... frac[Prec-1]
355-
* <----------| exponent == -2
366+
* 0 . 0000 0000 | frac[0] ... frac[n-1] |
367+
* |<----------| exponent == -2 |
368+
* |---------------------------------->| precision
369+
* |---------------------------------->| scale
356370
*
357-
* Conversely, when the exponent is positive, the decimal point moves to rightward.
358371
*
359-
* | frac[0] frac[1] frac[2] . frac[3] frac[4] ... frac[Prec-1]
360-
* |------------------------> exponent == 3
372+
* Conversely, when the exponent is positive, the decimal point moves to
373+
* rightward. In this case, the scale equals to
374+
*
375+
* BASE_FIG * (n - exponent) - ntz.
376+
*
377+
* the precision equals to
378+
*
379+
* scale + BASE_FIG * exponent - nlz.
380+
*
381+
* | frac[0] frac[1] . frac[2] ... frac[n-1] |
382+
* |---------------->| exponent == 2 |
383+
* | |---------------------->| scale
384+
* |---------------------------------------->| precision
361385
*/
362386

363387
ssize_t ex = p->exponent;
364388

365389
/* Count the number of decimal digits before frac[1]. */
366-
ssize_t precision = BASE_FIG; /* The number of decimal digits in frac[0]. */
390+
ssize_t n_digits_head = BASE_FIG;
367391
if (ex < 0) {
368-
precision += -ex * BASE_FIG; /* The number of leading zeros before frac[0]. */
369-
ex = 0;
392+
n_digits_head += (-ex) * BASE_FIG; /* The number of leading zeros before frac[0]. */
393+
ex = 0;
370394
}
371395
else if (ex > 0) {
372-
/* Count the number of decimal digits without the leading zeros in
373-
* the most significant digit in the integral part. */
374-
DECDIG x = p->frac[0];
375-
for (precision = 0; x > 0; x /= 10) {
376-
++precision;
377-
}
396+
/* Count the number of decimal digits without the leading zeros in
397+
* the most significant digit in the integral part.
398+
*/
399+
n_digits_head -= nlz; /* Make the number of digits */
378400
}
379401

380-
/* Count the number of decimal digits after frac[0]. */
381-
if (ex > (ssize_t)p->Prec) {
382-
/* In this case the number is an integer with multiple trailing zeros. */
383-
precision += (ex - 1) * BASE_FIG;
402+
if (out_precision) {
403+
ssize_t precision = n_digits_head;
404+
405+
/* Count the number of decimal digits after frac[0]. */
406+
if (ex > (ssize_t)n) {
407+
/* In this case the number is an integer with some trailing zeros. */
408+
precision += (ex - 1) * BASE_FIG;
409+
}
410+
else if (n > 0) {
411+
precision += (n - 1) * BASE_FIG;
412+
413+
if (ex < (ssize_t)n) {
414+
precision -= ntz;
415+
}
416+
}
417+
418+
*out_precision = precision;
384419
}
385-
else if (p->Prec > 0) {
386-
ssize_t n = (ssize_t)p->Prec - 1;
387-
while (n > 0 && p->frac[n] == 0) --n; /* Skip trailing zeros, just in case. */
388420

389-
precision += n * BASE_FIG;
421+
if (out_scale) {
422+
ssize_t scale = 0;
390423

391-
if (ex < (ssize_t)p->Prec) {
392-
DECDIG x = p->frac[n];
393-
for (; x > 0 && x % 10 == 0; x /= 10) {
394-
--precision;
395-
}
424+
if (p->exponent < 0) {
425+
scale = n_digits_head + (n - 1) * BASE_FIG - ntz;
426+
}
427+
else if (n > p->exponent) {
428+
scale = (n - p->exponent) * BASE_FIG - ntz;
396429
}
430+
431+
*out_scale = scale;
397432
}
433+
}
398434

435+
/*
436+
* call-seq:
437+
* precision -> integer
438+
*
439+
* Returns the number of decimal digits in +self+:
440+
*
441+
* BigDecimal("0").precision # => 0
442+
* BigDecimal("1").precision # => 1
443+
* BigDecimal("1.1").precision # => 2
444+
* BigDecimal("3.1415").precision # => 5
445+
* BigDecimal("-1e20").precision # => 21
446+
* BigDecimal("1e-20").precision # => 20
447+
* BigDecimal("Infinity").precision # => 0
448+
* BigDecimal("-Infinity").precision # => 0
449+
* BigDecimal("NaN").precision # => 0
450+
*
451+
*/
452+
static VALUE
453+
BigDecimal_precision(VALUE self)
454+
{
455+
ssize_t precision;
456+
BigDecimal_count_precision_and_scale(self, &precision, NULL);
399457
return SSIZET2NUM(precision);
400458
}
401459

460+
/*
461+
* call-seq:
462+
* scale -> integer
463+
*
464+
* Returns the number of decimal digits following the decimal digits in +self+.
465+
*
466+
* BigDecimal("0").scale # => 0
467+
* BigDecimal("1").scale # => 1
468+
* BigDecimal("1.1").scale # => 1
469+
* BigDecimal("3.1415").scale # => 4
470+
* BigDecimal("-1e20").precision # => 0
471+
* BigDecimal("1e-20").precision # => 20
472+
* BigDecimal("Infinity").scale # => 0
473+
* BigDecimal("-Infinity").scale # => 0
474+
* BigDecimal("NaN").scale # => 0
475+
*/
476+
static VALUE
477+
BigDecimal_scale(VALUE self)
478+
{
479+
ssize_t scale;
480+
BigDecimal_count_precision_and_scale(self, NULL, &scale);
481+
return SSIZET2NUM(scale);
482+
}
483+
402484
static VALUE
403485
BigDecimal_n_significant_digits(VALUE self)
404486
{
405487
ENTER(1);
406488

407489
Real *p;
408490
GUARD_OBJ(p, GetVpValue(self, 1));
409-
410-
ssize_t n = p->Prec;
411-
while (n > 0 && p->frac[n-1] == 0) --n;
412-
if (n <= 0) {
491+
if (VpIsZero(p) || !VpIsDef(p)) {
413492
return INT2FIX(0);
414493
}
415494

416-
int nlz, ntz;
495+
ssize_t n = p->Prec; /* The length of frac without trailing zeros. */
496+
for (n = p->Prec; n > 0 && p->frac[n-1] == 0; --n);
497+
if (n == 0) return INT2FIX(0);
417498

418-
DECDIG x = p->frac[0];
419-
for (nlz = BASE_FIG; x > 0; x /= 10) --nlz;
499+
DECDIG x;
500+
int nlz = BASE_FIG;
501+
for (x = p->frac[0]; x > 0; x /= 10) --nlz;
420502

421-
x = p->frac[n-1];
422-
for (ntz = 0; x > 0 && x % 10 == 0; x /= 10) ++ntz;
503+
int ntz = 0;
504+
for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz;
423505

424-
ssize_t n_digits = BASE_FIG * n - nlz - ntz;
425-
return SSIZET2NUM(n_digits);
506+
ssize_t n_significant_digits = BASE_FIG*n - nlz - ntz;
507+
return SSIZET2NUM(n_significant_digits);
426508
}
427509

428510
/*
@@ -4129,6 +4211,7 @@ Init_bigdecimal(void)
41294211
/* instance methods */
41304212
rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0);
41314213
rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0);
4214+
rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0);
41324215
rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0);
41334216

41344217
rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2);

test/bigdecimal/test_bigdecimal.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,6 +2070,59 @@ def test_precision_special
20702070
end
20712071
end
20722072

2073+
def test_scale_only_integer
2074+
assert_equal(0, BigDecimal(0).scale)
2075+
assert_equal(0, BigDecimal(1).scale)
2076+
assert_equal(0, BigDecimal(-1).scale)
2077+
assert_equal(0, BigDecimal(10).scale)
2078+
assert_equal(0, BigDecimal(-10).scale)
2079+
assert_equal(0, BigDecimal(100_000_000).scale)
2080+
assert_equal(0, BigDecimal(-100_000_000).scale)
2081+
assert_equal(0, BigDecimal(100_000_000_000).scale)
2082+
assert_equal(0, BigDecimal(-100_000_000_000).scale)
2083+
assert_equal(0, BigDecimal(100_000_000_000_000_000_000).scale)
2084+
assert_equal(0, BigDecimal(-100_000_000_000_000_000_000).scale)
2085+
assert_equal(0, BigDecimal("111e100").scale)
2086+
assert_equal(0, BigDecimal("-111e100").scale)
2087+
end
2088+
2089+
def test_scale_only_fraction
2090+
assert_equal(1, BigDecimal("0.1").scale)
2091+
assert_equal(1, BigDecimal("-0.1").scale)
2092+
assert_equal(2, BigDecimal("0.01").scale)
2093+
assert_equal(2, BigDecimal("-0.01").scale)
2094+
assert_equal(2, BigDecimal("0.11").scale)
2095+
assert_equal(2, BigDecimal("-0.11").scale)
2096+
assert_equal(21, BigDecimal("0.000_000_000_000_000_000_001").scale)
2097+
assert_equal(21, BigDecimal("-0.000_000_000_000_000_000_001").scale)
2098+
assert_equal(100, BigDecimal("111e-100").scale)
2099+
assert_equal(100, BigDecimal("-111e-100").scale)
2100+
end
2101+
2102+
def test_scale_full
2103+
assert_equal(1, BigDecimal("0.1").scale)
2104+
assert_equal(1, BigDecimal("-0.1").scale)
2105+
assert_equal(2, BigDecimal("0.01").scale)
2106+
assert_equal(2, BigDecimal("-0.01").scale)
2107+
assert_equal(2, BigDecimal("0.11").scale)
2108+
assert_equal(2, BigDecimal("-0.11").scale)
2109+
assert_equal(2, BigDecimal("11111e-2").scale)
2110+
assert_equal(2, BigDecimal("-11111e-2").scale)
2111+
assert_equal(18, BigDecimal("100.000_000_000_000_000_001").scale)
2112+
assert_equal(18, BigDecimal("-100.000_000_000_000_000_001").scale)
2113+
end
2114+
2115+
def test_scale_special
2116+
BigDecimal.save_exception_mode do
2117+
BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false)
2118+
BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false)
2119+
2120+
assert_equal(0, BigDecimal("Infinity").scale)
2121+
assert_equal(0, BigDecimal("-Infinity").scale)
2122+
assert_equal(0, BigDecimal("NaN").scale)
2123+
end
2124+
end
2125+
20732126
def test_n_significant_digits_only_integer
20742127
assert_equal(0, BigDecimal(0).n_significant_digits)
20752128
assert_equal(1, BigDecimal(1).n_significant_digits)

0 commit comments

Comments
 (0)