Set all BigDecimal operations to a certain precision?

回眸只為那壹抹淺笑 提交于 2021-02-07 05:20:20

问题


My Java program is centered around high precision calculations, which need to be accurate to at least 120 decimal places.
Consequentially, all non-integer numbers will be represented by BigDecimals in the program.

Obviously I need to specify the accuracy of the rounding for the BigDecimals, to avoid infinite decimal expressions etc.
Currently, I find it a massive nuisance to have to specify the accuracy at every instantiation or mathematical operation of a BigDecimal.

Is there a way to set a 'global accuracy' for all BigDecimal calculations?
(Such as the Context.prec() for the Decimal module in python)

Thanks


Specs:
Java jre7 SE
Windows 7 (32)


回答1:


(Almost) Original

Not as simple, but you can create a MathContext and pass it to all your BigDecimal constructors and the methods performing operations.

Revised

Alternatively, you can extend BigDecimal and override any operations you want to use by supplying the right MathContext, and using the rounding version of 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:


Create a BigDecimalFactory class with static factory methods matching all constructors that accept MathContext - except that the MathContext instance is inside the factory and statically initialized at startup time. Here's a fragment:

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:


Is there a way to set a 'global accuracy' for all BigDecimal calculations?

No.

You'll have to create a wrapper class that has a MathContext as an extra attribute. It will need to:

  • use this mc for each mathematical operation that would otherwise use the default semantics, and

  • create and return another wrapped instance each time the wrapped operation returns a regular instance.

(As a variation, you could implement a 'global' MathContext using a static, but you'll still need to use wrappering to ensure that the mc is used.)

(Extending BigDecimal would work too, and that is arguable neater than a wrapper class.)


You said this in a comment:

I really don't want to write my own Decimal module, I just want to understand why BigDecimal is being so uncooperative.

(Design questions can only be answered definitively by the design team. However ...)

As with all complicated utility classes, the design of BigDecimal is a compromise that is designed to meet the requirements of a wide range of use-cases. It is also a compromise between the competing meta-requirements (wrong word) of "powerfulness" and "simplicity".

What you have is a use-case that is not particularly well supported. But I suspect that if it was well supported (e.g. with a global MathContext controlling everything or a MathContext attached to each BigDecimal) then that would introduce all sorts of other complexities; e.g. dealing with operations where there are two or more competing context objects to consider. Such problems could be dealt with ... but they are liable to lead to "surprises" for the programmer, and that is not a good thing.

The current approach is simple and easy to understand, and if you need something more complicated you can implement it ... by explicitly supplying a MathContext for the operations that require it.




回答4:


You could create a class that extends BigDecimal and sets the precision automatically for you. Then you just use you that class.

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



回答5:


You can create a wrapper for BigDecimals, which will do this job:

 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
 }

In this case you don't have to override all operation of BigDecimal (in extending case), just ones you use. It gives you control over all instances and doesn't force to follow BigDecimal contract.




回答6:


You'll have to create your own wrapper class that has your default MathContext like this full example:

In this example, I used the library (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());
    }

}

Test:

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:


You can use the BigDecimal setScale function!

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


来源:https://stackoverflow.com/questions/10060158/set-all-bigdecimal-operations-to-a-certain-precision

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!