Set all BigDecimal operations to a certain precision?

19,719

Solution 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 ) );
      }
      ...
}

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

Solution 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.

Solution 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);
      }
      ...
}
Share:
19,719
Anti Earth
Author by

Anti Earth

Maths is perfect; I am not.

Updated on June 04, 2022

Comments

  • Anti Earth
    Anti Earth almost 2 years

    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)

  • Anti Earth
    Anti Earth about 12 years
    I would still have to perform this at every instantiation of a BigDecimal.
  • Anti Earth
    Anti Earth about 12 years
    That's exactly what I want to avoid.
  • Anti Earth
    Anti Earth about 12 years
    I'm actually having some difficulty using the class extension. If I have 2 BigDecimals representing "1" and "3", both of which have scale set to 120... Dividing "1" by "3" still produces a non-terminating decimal, even if I assign the result a scale of 120 as well! (Which was what I was originally trying to avoid; extra parameters to math operations).
  • trutheality
    trutheality about 12 years
    I think that you can fix that by similarly overriding the divide method for your class.
  • Anti Earth
    Anti Earth about 12 years
    I really don't want to write my own Decimal module, I just want to understand why BigDecimal is being so uncooperative./
  • Stephen C
    Stephen C about 12 years
    This won't work ... if I read the javadoc correctly. The primary reason is that the constructor you are using uses the MathContext for the conversion only. It is not attached to the resulting BigDecimal object.
  • trutheality
    trutheality about 12 years
    It's trying to be as exact as possible, which means trying to never round unless asked to. The divide override is simple, you just wrap the rounding divide. Now that I think about it, it would be a good idea to override other operations are using to make sure that they are returning an instance of your class and not a regular BigDecimal.
  • Anti Earth
    Anti Earth about 12 years
    Yep. There's no need to me to even override a constructor than is there? (By default, won't a string passed to the constructor be converted to a BigDecimal of the exact value of the string?) Thanks
  • Stephen C
    Stephen C about 12 years
    That means that the precision constraint won't apply to operations performed on the BigDecimal instances created with the factory ... unless you supply the MathContext as an argument to to relevant operation.
  • trutheality
    trutheality about 12 years
    You'll need to override (well, implement, technically you never override a constructor) the string constructor. Constructors don't get inherited automatically.
  • Stephen C
    Stephen C about 12 years
    This only works if the wrapper also wraps all of the BigDecimal operations that create a new instance.
  • sparc_spread
    sparc_spread about 12 years
    Whatever you do, don't use my factory approach! @StephenC's comments on it are correct - the MathContext passed to the constructors does not influence later operations, only the conversion performed in the constructor.
  • sparc_spread
    sparc_spread about 12 years
    Argh! You're right. I mentioned your comments in one of the other threads in this post so that people know.
  • Anti Earth
    Anti Earth about 12 years
    I'll be honest, I now have no ideas on how to implement this. Can somebody provide some example code? (show constructor and divide override methods?)
  • Anti Earth
    Anti Earth about 12 years
    I see. I'm still unsure why I need to superclass the constructor. I don't need exactly 120 decimal places of accuracy, just at least 120. Should I then only re-implement the divide function, the only concern being non terminating decimals, and have the rest accurate to whatever it turns out to be?
  • trutheality
    trutheality about 12 years
    Unfortunately, you will still need to superclass the constructor if you want to use a number.divide(otherNumber); syntax, because you need number to be of type MyBigDecimal and not BigDecimal. Another option is to just forget about subclassing and write a static BigDecimal divide( BigDecimal a, BigDecimal b ){ a.divide(b,context);}; function and use it instead of the one provided.
  • Anti Earth
    Anti Earth about 12 years
    That's exactly what I've been thinking :) Thankyou sir!
  • Stephen C
    Stephen C about 10 years
    @trutheality - "Superclassing" a constructor is bad terminology. Constructors are not inheritable in Java ...
  • SatA
    SatA almost 10 years
    This wont work. From the JavaDocs on 'setScale': "Note that since BigDecimal objects are immutable, calls of this method do not result in the original object being modified, contrary to the usual convention of having methods named setX mutate field X. Instead, setScale returns an object with the proper scale; the returned object may or may not be newly allocated." So calling it in the constructor won't do anything, at least in some situations.