Устанавливать все операции BigDecimal на определенную точность?

Моя программа Java сосредоточена на вычислениях высокой точности, которые должны быть точными, по меньшей мере, на 120 знаков после запятой.
Соответственно, все нецелые числа будут представлены BigDecimals в программе.

Очевидно, мне нужно указать точность округления для BigDecimals, чтобы избежать бесконечных десятичных выражений и т.д.
В настоящее время я нахожу, что огромная неприятность должна указывать точность при каждом экземпляре или математической операции BigDecimal.

Есть ли способ установить "глобальную точность" для всех вычислений BigDecimal?
(Например, Context.prec() для десятичного модуля в python)

Спасибо


Технические характеристики:
Java jre7 SE
Windows 7 (32)

Ответ 1

(почти) Оригинал

Не так просто, но вы можете создать MathContext и передать его всем вашим конструкторам BigDecimal и методам, выполняющим операции.

Исправленный вариант

В качестве альтернативы вы можете расширить BigDecimal и переопределить любые операции, которые вы хотите использовать, указав правильный MathContext и используя версию округления divide:

public class MyBigDecimal extends BigDecimal {

      private static MathContext context = new MathContext(120, RoundingMode.HALF_UP);

      public MyBigDecimal(String s) {
           super(s, context);
      }
      public MyBigDecimal(BigDecimal bd) {
           this(bd.toString()); // (Calls other constructor)
      }
      ...
      public MyBigDecimal divide( BigDecimal divisor ){
           return new MyBigDecimal( super.divide( divisor, context ) );
      }
      public MyBigDecimal add( BigDecimal augend ){
           return new MyBigDecimal( super.add( augend ) );
      }
      ...
}

Ответ 2

Создайте класс BigDecimalFactory со статическими factory методами, соответствующими всем конструкторам, принимающим MathContext - за исключением того, что экземпляр MathContext находится внутри factory и статически инициализирован во время запуска. Здесь фрагмент:

public class BigDecimalFactory {
    public static BigDecimal newInstance (BigInteger unscaledVal, int scale) {
        return new BigDecimal (unscaledVal, scale, _mathContext);
    }

    // . . . other factory methods for other BigDecimal constructors

    private static final MathContext _mathContext = 
        new MathContext (120, BigDecimal.ROUND_HALF_UP);
}

Ответ 3

Есть ли способ установить "глобальную точность" для всех вычислений BigDecimal?

Нет.

Вам необходимо создать класс-оболочку с MathContext в качестве дополнительного атрибута. Он должен будет:

  • используйте эту mc для каждой математической операции, которая в противном случае использовала бы семантику по умолчанию, а

  • создавать и возвращать другой завернутый экземпляр каждый раз, когда завершенная операция возвращает обычный экземпляр.

(В качестве варианта вы можете реализовать "глобальный" MathContext с помощью статики, но вам все равно нужно использовать обертку, чтобы убедиться, что используется mc.)

(Расширение BigDecimal тоже будет работать, и это возможно более аккуратно, чем класс-оболочка.)


Вы сказали это в комментарии:

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

(Вопросы дизайна могут быть окончательно решены командой дизайнеров. Однако...)

Как и во всех сложных классах утилиты, дизайн BigDecimal является компромиссом, который разработан для удовлетворения потребностей широкого круга прецедентов. Это также компромисс между конкурирующими мета-требованиями (неправильное слово) "мощьности" и "простоты".

У вас есть прецедент, который не особенно хорошо поддерживается. Но я подозреваю, что если он был хорошо поддержан (например, с глобальным MathContext, управляющим всем или MathContext, прикрепленным к каждому BigDecimal), тогда это приведет к возникновению всех видов других сложностей; например работа с операциями, в которых рассматриваются два или более конкурирующих контекстных объекта. Такие проблемы можно было бы решить... но они могут привести к "неожиданностям" для программиста, и это не очень хорошо.

Современный подход прост и понятен, и если вам нужно что-то более сложное, вы можете его реализовать... путем явного предоставления MathContext для требующих его операций.

Ответ 4

Вы можете создать класс, который расширяет BigDecimal и автоматически устанавливает точность для вас. Тогда вы просто используете этот класс.

public class MyBigDecimal extends BigDecimal {
      public MyBigDecimal(double d) {
           super(d);
           this.setScale(120, BigDecimal.ROUND_HALF_UP);
      }
      ...
}

Ответ 5

Вы можете создать обертку для BigDecimals, которая будет выполнять эту работу:

 class BigDecimalWrapper {
     BigDecimal bd;   

     BigDecimalWrapper add(BigDecimalWrapper another) {
         BigDecimal r = this.bd.add(another.bd);
         r.setScale(...);
         bd = r;
         return this;
     }
     // and so on for other operations
 }

В этом случае вам не нужно переопределять всю операцию BigDecimal (в расширении), только те, которые вы используете. Он дает вам контроль над всеми экземплярами и не принуждает следовать контракту BigDecimal.

Ответ 6

Вам нужно будет создать свой собственный класс-оболочку с MathContext по умолчанию, как в следующем полном примере:

В этом примере я использовал библиотеку (https://projectlombok.org)

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BigDecimalFactory {

    //Seguindo a precisão máxima do PowerBuilder e Oracle Database
    public static final MathContext DEFAULT_CONTEXT = new MathContext(120 , RoundingMode.HALF_UP);
    private static final BigDecimalFactory FACTORY = new BigDecimalFactory();

    private class SBigDecimal extends BigDecimal {

        public SBigDecimal(BigDecimal bdNumber) {
            super(bdNumber.toPlainString());
        }

        public SBigDecimal(String stringNumber) {
            super(stringNumber);
        }

        @Override
        public BigDecimal divide(BigDecimal divisor) {
            return new SBigDecimal(super.divide(divisor, DEFAULT_CONTEXT).stripTrailingZeros());
        }

        @Override
        public BigDecimal divide(BigDecimal divisor, MathContext mc) {
            return new SBigDecimal(super.divide(divisor, mc));
        }

        @Override
        public BigDecimal divideToIntegralValue(BigDecimal divisor) {
            return new SBigDecimal(super.divideToIntegralValue(divisor));
        }

        @Override
        public BigDecimal divideToIntegralValue(BigDecimal divisor, MathContext mc) {
            return new SBigDecimal(super.divideToIntegralValue(divisor, mc));
        }

        @Override
        public BigDecimal remainder(BigDecimal divisor) {
            return new SBigDecimal(super.remainder(divisor));
        }

        @Override
        public BigDecimal remainder(BigDecimal divisor, MathContext mc) {
            return new SBigDecimal(super.remainder(divisor, mc));
        }

        @Override
        public BigDecimal pow(int n) {
            return new SBigDecimal(super.pow(n));
        }

        @Override
        public BigDecimal pow(int n, MathContext mc) {
            return new SBigDecimal(super.pow(n, mc));
        }

        @Override
        public BigDecimal abs() {
            return new SBigDecimal(super.abs());
        }

        @Override
        public BigDecimal abs(MathContext mc) {
            return new SBigDecimal(super.abs(mc));
        }

        @Override
        public BigDecimal negate() {
            return new SBigDecimal(super.negate());
        }

        @Override
        public BigDecimal negate(MathContext mc) {
            return new SBigDecimal(super.negate(mc));
        }

        @Override
        public BigDecimal plus() {
            return new SBigDecimal(super.plus());
        }

        @Override
        public BigDecimal plus(MathContext mc) {
            return new SBigDecimal(super.plus(mc));
        }

        @Override
        public BigDecimal round(MathContext mc) {
            return new SBigDecimal(super.round(mc));
        }

        @Override
        public BigDecimal setScale(int newScale, RoundingMode roundingMode) {
            return new SBigDecimal(super.setScale(newScale, roundingMode));
        }

        @Override
        public BigDecimal setScale(int newScale, int roundingMode) {
            return new SBigDecimal(super.setScale(newScale, roundingMode));
        }

        @Override
        public BigDecimal setScale(int newScale) {
            return new SBigDecimal(super.setScale(newScale));
        }

        @Override
        public BigDecimal movePointLeft(int n) {
            return new SBigDecimal(super.movePointLeft(n));
        }

        @Override
        public BigDecimal movePointRight(int n) {
            return new SBigDecimal(super.movePointRight(n));
        }

        @Override
        public BigDecimal scaleByPowerOfTen(int n) {
            return new SBigDecimal(super.scaleByPowerOfTen(n));
        }

        @Override
        public BigDecimal stripTrailingZeros() {
            return new SBigDecimal(super.stripTrailingZeros());
        }

        @Override
        public BigDecimal min(BigDecimal val) {
            return new SBigDecimal(super.min(val));
        }

        @Override
        public BigDecimal max(BigDecimal val) {
            return new SBigDecimal(super.max(val));
        }

        @Override
        public BigDecimal ulp() {
            return new SBigDecimal(super.ulp());
        }

        @Override
        public BigDecimal add(BigDecimal augend, MathContext mc) {
            return new SBigDecimal(super.add(augend, mc));
        }

        @Override
        public BigDecimal subtract(BigDecimal subtrahend) {
            return new SBigDecimal(super.subtract(subtrahend));
        }

        @Override
        public BigDecimal subtract(BigDecimal subtrahend, MathContext mc) {
            return new SBigDecimal(super.subtract(subtrahend, mc));
        }

        @Override
        public BigDecimal multiply(BigDecimal multiplicand) {
            return new SBigDecimal(super.multiply(multiplicand));
        }

        @Override
        public BigDecimal multiply(BigDecimal multiplicand, MathContext mc) {
            return new SBigDecimal(super.multiply(multiplicand, mc));
        }

        @Override
        public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) {
            return new SBigDecimal(super.divide(divisor, scale, roundingMode));
        }

        @Override
        public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) {
            return new SBigDecimal(super.divide(divisor, scale, roundingMode));
        }

        @Override
        public BigDecimal divide(BigDecimal divisor, int roundingMode) {
            return new SBigDecimal(super.divide(divisor, roundingMode));
        }

        @Override
        public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) {
            return new SBigDecimal(super.divide(divisor, roundingMode));
        }

        @Override
        public BigDecimal add(BigDecimal augend) {
            return new SBigDecimal(super.add(augend));
        }
    }

    public BigDecimal internalCreate(String stringNumber) {
        return new SBigDecimal(stringNumber);
    }

    public static BigDecimal create(BigDecimal b) {
        return FACTORY.internalCreate(b.toString());
    }

    public static BigDecimal create(String stringNumber) {
        return FACTORY.internalCreate(stringNumber);
    }

    public static BigDecimal create(Long longNumber) {
        return FACTORY.internalCreate(longNumber.toString());
    }

    public static BigDecimal create(Integer doubleNumber) {
        return FACTORY.internalCreate(doubleNumber.toString());
    }

    public static BigDecimal create(Double doubleNumber) {
        return FACTORY.internalCreate(doubleNumber.toString());
    }

}

Тестовое задание:

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class JavaTeste {

    public static void main(String args[]){

        //1) With your own BigDecimal
        BigDecimal b1 = BigDecimalFactory.create("100");
        BigDecimal b2 = BigDecimalFactory.create("25");
        BigDecimal b3 = BigDecimalFactory.create("13");
        System.out.println(b1.divide(b2));
        System.out.println(b1.divide(b3));


        //2) Without your own BigDecimal
        MathContext mathContext = new MathContext(38, RoundingMode.HALF_UP);
        BigDecimal b01 = new BigDecimal("100", mathContext);
        BigDecimal b02 = new BigDecimal("25", mathContext);
        BigDecimal b03 = new BigDecimal("13", mathContext);
        System.out.println(b01.divide(b02));
        System.out.println(b01.divide(b03, mathContext));

        //3) And this just not work
        BigDecimal b001 = new BigDecimal("100");
        BigDecimal b002 = new BigDecimal("25");
        BigDecimal b003 = new BigDecimal("13");
        System.out.println(b001.divide(b002));
        System.out.println(b001.divide(b003));

    }

}

Ответ 7

Вы можете использовать функцию BigDecimal setScale!

BigDecimal db = new BigDecimal(<number>).setScale(120, BigDecimal.ROUND_HALF_UP); (or down)