Любой аккуратный способ ограничить значимые цифры с BigDecimal - PullRequest
6 голосов
/ 27 сентября 2011

Я хочу округлить Java BigDecimal до определенного количества значащих цифр (НЕ десятичных знаков), например, до 4 цифр:

12.3456 => 12.35
123.456 => 123.5
123456 => 123500

и т.д.. Основная проблема заключается в том, как найти порядок величины BigDecimal, чтобы я мог затем решить, сколько мест использовать после десятичной точки.

Все, что я могу вспомнить, это какой-то ужасный цикл, деленный на 10, пока результат не станет <1, я надеюсь, что есть лучший способ. </p>

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

Ответы [ 7 ]

11 голосов
/ 27 сентября 2011

Самое простое решение:

  int newScale = 4-bd.precision()+bd.scale();
  BigDecimal bd2 = bd1.setScale(newScale, RoundingMode.HALF_UP);

Преобразование строки не требуется, оно основано исключительно на BigDecimal арифметике и, следовательно, максимально эффективно, вы можете выбрать RoundingMode, и оно мало. Если выходные данные должны быть String, просто добавьте .toPlainString().

10 голосов
/ 27 сентября 2011

Почему бы просто не использовать round(MathContext)?

BigDecimal value = BigDecimal.valueOf(123456);
BigDecimal wantedValue = value.round(new MathContext(4, RoundingMode.HALF_UP));
5 голосов
/ 27 сентября 2011

Вы можете использовать следующие строки:

int digitsRemain = 4;

BigDecimal bd = new BigDecimal("12.3456");
int power = bd.precision() - digitsRemain;
BigDecimal unit = bd.ulp().scaleByPowerOfTen(power);
BigDecimal result = bd.divideToIntegralValue(unit).multiply(unit);

Примечание: это решение всегда округляется до последней цифры.

2 голосов
/ 27 сентября 2011

Кто-то, вероятно, найдет лучшее решение, но первое, что приходит на ум, - это забросить его в StringBuilder, проверить, содержит ли оно '.'и вернуть соответствующую подстроку длины.Например:

int n = 5;
StringBuilder sb = new StringBuilder();
sb.append("" + number);
if (sb.indexOf(".") > 0)
{
    n++;
}
BigDecimal result = new BigDecimal(sb.substring(0, n));
0 голосов
/ 12 апреля 2018

Ответ AH технически верен, но вот более общее (и более простое для понимания) решение:

import static org.bitbucket.cowwoc.requirements.core.Requirements.assertThat;

/**
 * @param value            a BigDecimal
 * @param desiredPrecision the desired precision of {@code value}
 * @param roundingMode     the rounding mode to use
 * @return a BigDecimal with the desired precision
 * @throws NullPointerException if any of the arguments are null
 */
public BigDecimal setPrecision(BigDecimal value, int desiredPrecision, RoundingMode roundingMode)
{
    assertThat("value", value).isNotNull();
    assertThat("roundingMode", roundingMode).isNotNull();
    int decreaseScaleBy = value.precision() - desiredPrecision;
    return value.setScale(value.scale() - decreaseScaleBy, roundingMode);
}
0 голосов
/ 27 сентября 2011

Поскольку BigDecimal в основном строка, возможно это:

import java.math.BigDecimal;

public class Silly {
    public static void main( String[] args ) {
        BigDecimal value = new BigDecimal("1.23238756843723E+5");
        String valueString = value.toPlainString();
        int decimalIndex = valueString.indexOf( '.' );
        System.out.println( value + " has " +
            (decimalIndex < 0 ? valueString.length() : decimalIndex) +
            " digits to the left of the decimal" );
    }
}

Который производит это:

123238.756843723 has 6 digits to the left of the decimal
0 голосов
/ 27 сентября 2011

Мне кажется, это так просто: Учитывая N = 5, D = 123,456789

  1. получить строковое представление числа "123.456789"
  2. получить первые N-1 цифры номера, «123,4»
  3. оценивают D [N] и D [N + 1], в данном случае «5» и «6»
  4. 6 соответствует критерию округления (6> 4), поэтому нести 1 и сделать D [N] = 5 + 1 = 6
  5. D после округления теперь 123,46

Порядок можно рассчитать с помощью Math.floor (Math.log (D)).

надеюсь, это поможет.

...