Skip to content

Commit 97f39e1

Browse files
committed
[ruby/bigdecimal] Add BigDecimal#scale
Fixes rubyGH-198. ruby/bigdecimal@4fbec55680
1 parent ba864f6 commit 97f39e1

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy