如何处理Java BigDecimal性能?

如何处理Java BigDecimal性能?,java,optimization,currency,math,bigdecimal,Java,Optimization,Currency,Math,Bigdecimal,我为生活编写货币交易应用程序,因此我必须处理货币值(遗憾的是Java仍然没有十进制浮点类型,也没有支持任意精度的货币计算)。“使用BigDecimal!”-你可能会说。我愿意。但是现在我有了一些代码,其中性能是一个问题,而BigDecimal比double原语慢1000多倍(!) 计算非常简单:系统所做的是多次计算a=(1/b)*c(其中a,b和c是定点值)。然而,问题在于此(1/b)。我不能使用定点算法,因为没有定点。和BigDecimal result=a.multiply(BigDecim

我为生活编写货币交易应用程序,因此我必须处理货币值(遗憾的是Java仍然没有十进制浮点类型,也没有支持任意精度的货币计算)。“使用BigDecimal!”-你可能会说。我愿意。但是现在我有了一些代码,其中性能是一个问题,而BigDecimal比
double
原语慢1000多倍(!)

计算非常简单:系统所做的是多次计算
a=(1/b)*c
(其中
a
b
c
是定点值)。然而,问题在于此
(1/b)
。我不能使用定点算法,因为没有定点。和
BigDecimal result=a.multiply(BigDecimal.ONE.divide(b).multiply(c)
不仅难看,而且慢得很

我可以用什么来代替BigDecimal?我需要至少10倍的性能提升。我发现其他方面都很好,它具有任意精度的算术,但它甚至比BigDecimal还要慢


有什么建议吗?

也许你应该先用a=c/b替换a=(1/b)*c?这不是10倍,但仍然是一些东西


如果我是你,我会创建我自己的类货币,它将保留长美元和长美分,并在其中进行数学运算。

将长数存储为美分数。例如,
BigDecimal Money=new BigDecimal(“4.20”)
变成了
long money=420
。你只需记住修改100就可以得到美元和美分的输出。如果你需要跟踪,比如说,十分之一美分,它就会变成
long money=4200

你可能想转到定点数学。现在只需搜索一些库。 在sourceforge上,我还没有深入研究这个问题


您是否使用org.jscience.economics.money进行了测试?因为这保证了准确性。固定点的准确性仅与分配给每个工件的位数一样,但速度很快。

1/b也不能用BigDecimal精确表示。请参阅API文档了解结果的舍入方式


基于一个或两个长字段编写自己的固定十进制类应该不会太困难。我不知道任何合适的现成库。

假设您可以以某种任意但已知的精度(比如十亿分之一美分)工作,并且有一个已知的需要处理的最大值(万亿美元?)您可以编写一个类,将该值存储为十亿分之一的整数。您需要两个long来表示它。这可能是使用double的十倍;大约是BigDecimal的一百倍

大多数操作只是对每个部分执行操作并重新规范化。除法稍微复杂一些,但不多

编辑:作为对注释的回应。你需要在你的类上实现一个位移位操作(很容易,因为长整数的乘法器是二的幂)。要除法移位除数,直到除数不太大于被除数;从被除数中减去移位除数,并增加结果(适当移位).重复


再次编辑:您可能会发现BigInteger可以满足您的需要。

您使用的是哪个版本的JDK/JRE

你也可以试着看看他们是否能帮你加快速度

编辑:


我记得在什么地方读过(我认为它是有效的Java)BigDecmal类在某个时候从被JNI调用为C库变为全Java…而且速度更快了。因此,可能是您使用的任何任意精度库都无法获得您所需的速度。

JNI是一种可能性吗?您可能能够恢复一些速度并潜在地利用现有的本机修复程序d点库(甚至可能还有一些SSE*goodness)


也许

就我个人而言,我认为BigDecimal并不适合这样做


您确实希望在内部使用long来表示最小的单位(即美分、10美分)来实现自己的Money类。在这方面需要做一些工作,实现
add()
divide()
等,但其实并不难。

您能提供有关计算目的的更多信息吗

你要处理的是速度和精度之间的折衷。如果你换成原语,精度损失会有多大

我认为在某些情况下,用户可能会以较低的精度换取速度,只要他们能够在需要时磨练精确的计算。这实际上取决于您将使用此计算的目的


也许您可以允许用户使用double快速预览结果,然后如果他们愿意,使用BigDecimal请求更精确的值?

也许您应该研究一下硬件加速的十进制算法


大多数双精度运算都能给你足够的精度。你可以用双精度表示10万亿美元,这对你来说可能已经足够了


在我工作过的所有交易系统中(四家不同的银行),他们使用了double并进行了适当的舍入。我不认为有任何理由使用BigDecimal。

我记得参加过IBM的一次销售演示,介绍了BigDecimal的硬件加速实现。因此,如果您的目标平台是IBM System z或System p,您可以无缝地利用它。

在eq中也有类似的问题uity交易系统可以追溯到99年。在设计之初,我们选择将系统中的每个数字表示为长乘以1000000,因此1.3423是1342300L。但这一点的主要驱动因素是内存足迹,而不是直线性能

警告一句,除非我真的确信数学性能非常关键,否则我今天不会再这样做了。在大多数bog标准Web应用程序中,jdbc访问和访问其他网络资源的开销
Only 10x performance increase desired for something that is 1000x slower than primitive?!.
 0% Scenario{vm=java, trial=0, benchmark=Double} 0.34 ns; ?=0.00 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=BigDecimal} 356.03 ns; ?=11.51 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=BigDecNoRecip} 301.91 ns; ?=14.86 ns @ 10 trials

    benchmark      ns linear runtime
       Double   0.335 =
   BigDecimal 356.031 ==============================
BigDecNoRecip 301.909 =========================

vm: java
trial: 0
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Random;

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;

public class BigDecimalTest {
  public static class Benchmark1 extends SimpleBenchmark {
    private static int ARRAY_SIZE = 131072;

    private Random r;

    private BigDecimal[][] bigValues = new BigDecimal[3][];
    private double[][] doubleValues = new double[3][];

    @Override
    protected void setUp() throws Exception {
      super.setUp();
      r = new Random();

      for(int i = 0; i < 3; i++) {
        bigValues[i] = new BigDecimal[ARRAY_SIZE];
        doubleValues[i] = new double[ARRAY_SIZE];

        for(int j = 0; j < ARRAY_SIZE; j++) {
          doubleValues[i][j] = r.nextDouble() * 1000000;
          bigValues[i][j] = BigDecimal.valueOf(doubleValues[i][j]); 
        }
      }
    }

    public double timeDouble(int reps) {
      double returnValue = 0;
      for (int i = 0; i < reps; i++) {
        double a = doubleValues[0][reps & 131071];
        double b = doubleValues[1][reps & 131071];
        double c = doubleValues[2][reps & 131071];
        double division = a * (1/b) * c; 
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }

    public BigDecimal timeBigDecimal(int reps) {
      BigDecimal returnValue = BigDecimal.ZERO;
      for (int i = 0; i < reps; i++) {
        BigDecimal a = bigValues[0][reps & 131071];
        BigDecimal b = bigValues[1][reps & 131071];
        BigDecimal c = bigValues[2][reps & 131071];
        BigDecimal division = a.multiply(BigDecimal.ONE.divide(b, MathContext.DECIMAL64).multiply(c));
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }

    public BigDecimal timeBigDecNoRecip(int reps) {
      BigDecimal returnValue = BigDecimal.ZERO;
      for (int i = 0; i < reps; i++) {
        BigDecimal a = bigValues[0][reps & 131071];
        BigDecimal b = bigValues[1][reps & 131071];
        BigDecimal c = bigValues[2][reps & 131071];
        BigDecimal division = a.multiply(c.divide(b, MathContext.DECIMAL64));
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }
  }

  public static void main(String... args) {
    Runner.main(Benchmark1.class, new String[0]);
  }
}
new DfpField(numberOfDigits).newDfp(myNormalNumber)
BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);