Determine Number of Decimal Place using BigDecimal

Viewed 57250

I was interested to have the following getNumberOfDecimalPlace function:

System.out.println("0 = " + Utils.getNumberOfDecimalPlace(0));          // 0
System.out.println("1.0 = " + Utils.getNumberOfDecimalPlace(1.0));      // 0
System.out.println("1.01 = " + Utils.getNumberOfDecimalPlace(1.01));    // 2
System.out.println("1.012 = " + Utils.getNumberOfDecimalPlace(1.012));  // 3
System.out.println("0.01 = " + Utils.getNumberOfDecimalPlace(0.01));    // 2
System.out.println("0.012 = " + Utils.getNumberOfDecimalPlace(0.012));  // 3

May I know how can I implement getNumberOfDecimalPlace, by using BigDecimal?

The following code doesn't work as expected:

public static int getNumberOfDecimalPlace(double value) {
    final BigDecimal bigDecimal = new BigDecimal("" + value);
    final String s = bigDecimal.toPlainString();
    System.out.println(s);
    final int index = s.indexOf('.');
    if (index < 0) {
        return 0;
    }
    return s.length() - 1 - index;
}

The following get printed :

0.0
0 = 1
1.0
1.0 = 1
1.01
1.01 = 2
1.012
1.012 = 3
0.01
0.01 = 2
0.012
0.012 = 3

However, for case 0, 1.0, it doesn't work well. I expect, "0" as result. But they turned out to be "0.0" and "1.0". This will return "1" as result.

12 Answers

This code:

int getNumberOfDecimalPlaces(BigDecimal bigDecimal) {
    String string = bigDecimal.stripTrailingZeros().toPlainString();
    int index = string.indexOf(".");
    return index < 0 ? 0 : string.length() - index - 1;
}

... passes these tests:

assertThat(getNumberOfDecimalPlaces(new BigDecimal("0.001")), equalTo(3));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("0.01")), equalTo(2));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("0.1")), equalTo(1));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1.000")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1.00")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1.0")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10.1")), equalTo(1));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10.01")), equalTo(2));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10.001")), equalTo(3));

... if that is indeed what you want. The other replies are correct, you have to use BigDecimal all the way through for this rather than double/float.

If you really get doubles i recommend formating them first as strings before creating the BigDecimal. At least that has worked for me: How to check if a double has at most n decimal places?

Depending on how many digits you expect you can either use standard formating like

String.valueOf(doubleValue);

or you could use specialised formatting to avoid exponential format

DecimalFormat decimalFormat = new DecimalFormat();
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE);
// don't use grouping for numeric-type cells
decimalFormat.setGroupingUsed(false);
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
value = decimalFormat.format(numericValue);

When you have a BigDecimal you can simply call scale() to get the number of decimal places.

It's not your code that's wrong, but your expectations. double is based on a binary floating point representation and completely unfit for accurately representing decimal fractions. Decimal 0.1 e.g. has an infinite number of digits when represented in binary, thus it gets truncated and when converted back to decimal, you get erros in the least significant digits.

If you use BigDecimal exclusively, your code will work as expected.

How about having a look at the javadoc of BigDecimal. I'm not sure, but I'd give getScale and getPercision a try.

Michael Borgwardt answer is the correct one. As soon as you use any double or float, your values are already corrupted.

To provide a code example:

System.out.println("0 = " + BigDecimalUtil.getNumberOfDecimalPlace("0")); // 0
System.out.println("1.0 = " + BigDecimalUtil.getNumberOfDecimalPlace("1.0")); // 0
System.out.println("1.01 = " + BigDecimalUtil.getNumberOfDecimalPlace(new BigDecimal("1.01"))); // 2
System.out.println("1.012 = " + BigDecimalUtil.getNumberOfDecimalPlace(new BigDecimal("1.012"))); // 3
System.out.println("0.01 = " + BigDecimalUtil.getNumberOfDecimalPlace("0.01")); // 2
System.out.println("0.012 = " + BigDecimalUtil.getNumberOfDecimalPlace("0.012")); // 3
System.out.println("0.00000000000000000012 = " + BigDecimalUtil.getNumberOfDecimalPlace("0.00000000000000000012")); // 20

And an overloaded version of getNumberOfDecimalPlace so you could use it with BigDecimal or String:

public static int getNumberOfDecimalPlace(String value) {
    final int index = value.indexOf('.');
    if (index < 0) {
        return 0;
    }
    return value.length() - 1 - index;
}

public static int getNumberOfDecimalPlace(BigDecimal value) {
    return getNumberOfDecimalPlace(value.toPlainString());
}

Why not just change your code to get a doubles decimal places?

public static int getNumberOfDecimalPlace(double value) {
    //For whole numbers like 0
    if (Math.round(value) == value) return 0;
    final String s = Double.toString(value);
    System.out.println(s);
    final int index = s.indexOf('.');
    if (index < 0) {
       return 0;
    }
    return s.length() - 1 - index;
}
Related