5

I do not want to limit the number of significant digits in a BigDecimal. I only want to find the number of significant digits that number has.

Is there a way to do this without converting the number to string and count the number characters?

0x56794E
  • 20,883
  • 13
  • 42
  • 58

3 Answers3

10

I believe you want a combination of stripTrailingZeros, precision and scale, as demonstrated here:

import java.math.*;

public class Test {

    public static void main(String[] args) {
        test("5000");      // 4
        test("5000.00");   // 4
        test("5000.12");   // 6
        test("35000");     // 5
        test("35000.00");  // 5
        test("35000.12");  // 7
        test("35000.120"); // 7
        test("0.0034");    // 2
        test("1.0034");    // 5
        test("1.00340");   // 5
    }


    private static void test(String input) {
        System.out.println(input + " => " +
            significantDigits(new BigDecimal(input)));
    }

    private static int significantDigits(BigDecimal input) {
        input = input.stripTrailingZeros();
        return input.scale() < 0
            ? input.precision() - input.scale()
            : input.precision(); 
    }
}

The call to stripTrailingZeros is required as otherwise it's entirely possible for a BigDecimal to be stored in a "non-normalized" form. For example, new BigDecimal(5000) has a precision of 4, not 1.

The call to scale() is used to handle cases where the normalized form has trailing zeroes before the decimal point, but nothing after the decimal point. In this case, the scale will always be negative, and indicates the number of trailing zeroes.

EDIT: Cases with trailing zeroes but no decimal point are inherently ambiguous - there's no definite number of significant digits to "5000" for example. The above code treats all trailing zeroes before the decimal point as significant.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • You're right in that `precision()` alone isn't the answer, but `new BigDecimal(8.6).precision()` somehow returns `50`, and `new BigDecimal(8.6).stripTrailingZeroes().precision()` also returns `50`. – rgettman Jan 29 '14 at 22:22
  • 1
    @rgettman: That's because `new BigDecimal(8.6)` isn't actually 8.6... it's the exact value of the nearest `double` to `8.6`. If you expected to get 2, you should use `new BigDecimal("8.6")`. Just print out `new BigDecimal(8.6)` and you'll see what I mean. – Jon Skeet Jan 29 '14 at 22:27
  • 3
    The other problem is that `new BigDecimal("5000.0").stripTrailingZeros().precision()` returns 1, but according to the accepted definitions you want it to return 5 (since you wouldn't add `.0` to the end of a number unless it were significant). Also, the number of significant digits in `"5000"` is ambiguous anyway. See "Significant figures" in Wikipedia. – ajb Jan 29 '14 at 22:28
  • @ajb: Ah, nice catch. I had *expected* a value of 1 there, thinking about it more in scientific terms (where 5000 = 5x10^3). – Jon Skeet Jan 29 '14 at 22:29
  • @Jon Actually, I tried to limit the number of significant digits so that it would fit into Decimal(3,2) in mysql database. But it appears that even though I enter 300, the data is out of range. Any suggestion? – 0x56794E Jan 29 '14 at 22:37
  • @abcXYZ: It's not clear what you mean by that, I'm afraid - but it seems to be outside the realm of this question. If you're interested in MySQL, I'd ask a question *about MySQL*. – Jon Skeet Jan 29 '14 at 22:39
  • @ajb: See my edit - do you think that fixes it? (I *think* it does, but I'd welcome a second opinion.) – Jon Skeet Jan 29 '14 at 22:39
  • @abcXYZ: I'm not sure what you mean. You haven't really told us what you're trying to achieve - and as I say, this question was only about BigDecimal. Ask a new question about MySQL... – Jon Skeet Jan 29 '14 at 23:07
  • @JonSkeet I think the correct answers for your test cases should be 1(?), 6, 6, 2(?), 7, 7, 8, 2, 5, 6. (The ? cases are ambiguous, and `BigDecimal` isn't capable of distinguishing between, say, a "5000" with one significant digit and a "5000" with two, for example.) – ajb Jan 29 '14 at 23:09
  • @ajb: Yes, this treats the ambiguity as "all digits before the decimal point are significant". I suspect that's as good as it's likely to get :( – Jon Skeet Jan 29 '14 at 23:11
1

The following modification of Jon's answer returns the results that seem correct to me:

private static int significantDigits(BigDecimal input) {
    return input.scale() <= 0
        ? input.precision() + input.stripTrailingZeros().scale()
        : input.precision();
}

(Note that input.stripTrailingZeros().scale() appears to always be negative in these tests.)

Also, as I noted above, BigDecimal isn't capable of distinguishing between, say, a "5000" with one significant digit and a "5000" with two, for example. Furthermore, according to the definitions, "5000." (with a trailing decimal point) should have exactly four significant digits, but BigDecimal isn't capable of handling that. (See http://en.wikipedia.org/wiki/Significant_figures for the definitions I'm using.)

ajb
  • 31,309
  • 3
  • 58
  • 84
0

Jon's answer is correct in most cases except exponential number:

private static int significantDigits(BigDecimal input) {
return input.scale() <= 0
    ? input.precision() + input.stripTrailingZeros().scale()
    : input.precision();
}

let's say the input as 1.230000E17, the function returns 18 however the correct significant digits should be 7.