Neat way to find the number of significant digits in a BigDecimal?
Asked Answered
T

3

7

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?

Tubman answered 29/1, 2014 at 22:9 Comment(0)
A
12

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.

Aluminium answered 29/1, 2014 at 22:19 Comment(10)
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.Filippo
@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.Aluminium
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.Approbation
@ajb: Ah, nice catch. I had expected a value of 1 there, thinking about it more in scientific terms (where 5000 = 5x10^3).Aluminium
@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?Tubman
@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.Aluminium
@ajb: See my edit - do you think that fixes it? (I think it does, but I'd welcome a second opinion.)Aluminium
@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...Aluminium
@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.)Approbation
@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 :(Aluminium
A
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.)

Approbation answered 29/1, 2014 at 23:16 Comment(0)
B
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.

Brookbrooke answered 23/11, 2018 at 8:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.