Определить количество десятичных мест с помощью BigDecimal

Мне было интересно иметь следующую функцию getNumberOfDecimalPlace:

    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

Могу ли я узнать, как я могу реализовать getNumberOfDecimalPlace, используя BigDecimal?

Следующий код не работает должным образом:

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;
}

Ниже напечатано следующее:

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

Однако для случая 0, 1.0 это не работает. Я ожидаю, что "0" - результат. Но они оказались "0.0" и "1.0". Это вернет результат "1".

Ответ 1

Этот код:

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

... проходит эти тесты:

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));

... если это действительно то, что вы хотите. Другие ответы правильны, вам нужно использовать BigDecimal для этого, а не double/float.

Ответ 2

Сочетание Turismo, Robert и user1777653's ответы, у нас есть:

int getNumberOfDecimalPlaces(BigDecimal bigDecimal) {
    return Math.max(0, bigDecimal.stripTrailingZeros().scale());
}
  • stripTrailingZeros() гарантирует, что конечные нули не учитываются (например, 1.0 имеет 0 знаков после запятой).
  • scale() более эффективен, чем String.indexOf().
  • Отрицательный scale() представляет нулевые десятичные разряды.

Там у вас это, лучшее из обоих миров.

Ответ 3

Это не ваш код, который неправильный, но ваши ожидания. double основан на двоичном представлении с плавающей запятой и полностью непригоден для точного представления десятичных дробей. Десятичный 0,1, например. имеет бесконечное число цифр, если оно представлено в двоичном формате, поэтому оно усекается и при преобразовании обратно в десятичное значение вы получаете erros в наименее значимых цифрах.

Если вы используете только BigDecimal, ваш код будет работать как ожидалось.

Ответ 4

Если вы действительно получаете парные пары, я рекомендую сначала их создать как строки перед созданием BigDecimal. По крайней мере, это сработало для меня: Как проверить, имеет ли double не более n десятичных знаков?

В зависимости от того, сколько цифр вы ожидаете, вы можете использовать стандартное форматирование, например

String.valueOf(doubleValue);

или вы можете использовать специализированное форматирование, чтобы избежать экспоненциального формата

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);

Когда у вас есть BigDecimal, вы можете просто вызвать scale(), чтобы получить число десятичных знаков.

Ответ 5

Попробуйте следующее:

Math.floor(Math.log(x) / Math.log(10))
0.001 = -3
0.01  = -2
0.1   = -1  
1     = 0  
10    = 1  
100   = 2

Ответ 6

Это должно сделать это

int getNumberOfDecimalPlace(BigDecimal number) {
    int scale = number.stripTrailingZeros().scale();
    return scale > 0 ? scale : 0;
}

Ответ 7

Как насчет просмотра javadoc BigDecimal. Я не уверен, но я бы дал getScale и getPercision попробовать.

Ответ 8

Лучший способ получить BigDecimal с указанным числом десятичных знаков - использовать метод setscale над ним. Лично мне нравится использовать версию округления метода (см. Ссылку ниже): http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html#setScale(int,%20int)

Если вы хотите получить число десятичных позиций, которое BigDecimal в настоящий момент задает при вызове связанного метода scale().

Ответ 9

Лучший вариант, который я нашел до сих пор (не нужен индекс toString +):

public static int digitsStripTrailingZero(BigDecimal value)
{
    return digits(value.stripTrailingZeros());
}

public static int digits(BigDecimal value)
{
    return Math.max(0, value.scale());
}

Ответ 10

Без необходимости конвертировать в String, более эффективно использовать масштаб непосредственно:

  private int getNumberOfDecimalPlaces(BigDecimal bigDecimal)
  {
    int scale =  bigDecimal.stripTrailingZeros().scale();
    return scale>0?scale:0;
  }

Ответ 11

Ответ Майкла Боргвардта правильный. Как только вы используете любой двойной или плавающий, ваши значения уже повреждены.

Чтобы предоставить пример кода:

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

И перегруженная версия getNumberOfDecimalPlace, чтобы вы могли использовать ее с BigDecimal или 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());
}

Ответ 12

Почему бы просто не изменить код, чтобы получить удвоение десятичных знаков?

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;
}