15

我的 Java 程序以高精度计算为中心,需要精确到至少 120 位小数。
因此,程序中的所有非整数都将由 BigDecimals 表示。

显然,我需要指定 BigDecimal 的舍入精度,以避免无限小数表达式等。
目前,我发现必须在 BigDecimal 的每个实例化或数学运算中指定精度是一件非常麻烦的事情。

有没有办法为所有 BigDecimal 计算设置“全局精度”?
(如pythonContext.prec()中的Decimal模块)

谢谢


规格:
Java jre7 SE
Windows 7 (32)

4

7 回答 7

13

(几乎)原创

没那么简单,但您可以创建一个MathContext并将其传递给您的所有BigDecimal构造函数和执行操作的方法。

修改

或者,您可以通过提供正确的 并使用 的舍入版本来扩展BigDecimal和覆盖您想要使用的任何操作:MathContextdivide

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 ) );
      }
      ...
}
于 2012-04-08T03:39:24.613 回答
4

BigDecimalFactory使用与所有接受的构造函数匹配的静态工厂方法创建一个类MathContext- 除了MathContext实例在工厂内部并在启动时静态初始化。这是一个片段:

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);
}
于 2012-04-08T03:45:21.193 回答
3

有没有办法为所有 BigDecimal 计算设置“全局精度”?

不。

您必须创建一个具有MathContext额外属性的包装类。它需要:

  • 将其mc用于每个将使用默认语义的数学运算,并且

  • 每次包装操作返回一个常规实例时,创建并返回另一个包装实例。

(作为一种变体,您可以MathContext使用静态实现“全局”,但您仍然需要使用包装来确保mc使用 。)

(扩展BigDecimal也可以,这可以说比包装类更整洁。)


你在评论中这样说:

我真的不想编写自己的 Decimal 模块,我只想了解 BigDecimal 为何如此不合作。

(设计问题只能由设计团队明确回答。但是......)

与所有复杂的实用程序类一样,BigDecimal 的设计是一种折衷方案,旨在满足各种用例的要求。这也是“强大”和“简单”这两个相互竞争的元要求(错误的词)之间的妥协。

您所拥有的是一个没有得到特别好的支持的用例。但我怀疑,如果它得到很好的支持(例如,全局MathContext控制一切或MathContext附加到每个BigDecimal),那么这将引入各种其他复杂性;例如,处理需要考虑两个或多个竞争上下文对象的操作。这样的问题是可以解决的……但它们很容易给程序员带来“惊喜”,这不是一件好事。

当前的方法简单易懂,如果您需要更复杂的东西,您可以实现它……通过MathContext为需要它的操作显式提供 a 。

于 2012-04-08T04:28:25.820 回答
2

您可以创建一个BigDecimal自动为您扩展和设置精度的类。然后你就用你那个类。

public class MyBigDecimal extends BigDecimal {
      public MyBigDecimal(double d) {
           super(d);
           this.setScale(120, BigDecimal.ROUND_HALF_UP);
      }
      ...
}
于 2012-04-08T03:40:27.150 回答
0

您可以为 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 合同。

于 2012-04-08T03:40:45.393 回答
0

您必须创建自己的包装类,该类具有默认的 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));

    }

}
于 2019-01-22T13:43:48.520 回答
-4

您可以使用 BigDecimal setScale 函数!

BigDecimal db = new BigDecimal(<number>).setScale(120, BigDecimal.ROUND_HALF_UP); (or down)
于 2012-04-08T03:38:15.570 回答