Ale*_*rev 59 java math optimization currency bigdecimal
我为生活编写货币交易应用程序,所以我必须处理货币价值(遗憾的是,Java仍然没有十进制浮动类型,并且没有任何东西可以支持任意精确的货币计算)."使用BigDecimal!" - 你可能会说.我做.但现在我有一些代码在性能是一个问题,BigDecimal为超过1000次(!)慢于double
原语.
计算很简单:系统做什么是计算a = (1/b) * c
很多很多次(其中a
,b
并c
有固定的点值).然而,问题在于此(1/b)
.我不能使用定点算术,因为没有固定点.而且BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c)
不仅丑陋,而且缓慢.
我可以用什么来代替BigDecimal?我需要至少10倍的性能提升.我发现了其他优秀的JScience库,它具有任意精度的算术,但它甚至比BigDecimal慢.
有什么建议?
Vla*_*hev 36
也许你应该开始用a = c/b替换a =(1/b)*c?这不是10倍,但仍然是一些东西.
如果我是你,我会创建自己的课程Money,这将保留很长的美元和长期美分,并在其中进行数学运算.
Pet*_*rey 15
大多数双重操作可以提供足够的精度.你可以代表10万亿美元的精确度和双倍,这可能对你来说已经足够了.
在我所有的交易系统(四个不同的银行)中,他们使用了双倍的适当舍入.我没有看到任何使用BigDecimal的理由.
dur*_*597 14
所以我原来的答案是错误的,因为我的基准写得很糟糕.我想我应该受到批评,而不是OP;)这可能是我写过的第一个基准之一......哦,这就是你学习的方式.而不是删除答案,这里是我没有测量错误的结果.一些说明:
BigDecimal.doubleValue()
,因为它是极其缓慢BigDecimal
s来搞乱结果.只返回一个值,并使用if语句来阻止编译器优化.但是,确保在大多数情况下让它工作以允许分支预测消除代码的这一部分.测试:
这是输出:
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
Run Code Online (Sandbox Code Playgroud)
这是代码:
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]);
}
}
Run Code Online (Sandbox Code Playgroud)
假设您可以使用一些任意但已知的精度(比如十亿分之一)并且具有已知的最大值,您需要处理(一万亿亿美元?),您可以编写一个类,将该值存储为十亿分之一的整数一分钱.你需要两个长的代表它.这应该是使用double的十倍慢; 大约是BigDecimal的一百倍.
大多数操作只是对每个部件执行操作并重新规范化.分部稍微复杂一些,但并不多.
编辑:回应评论.你需要在你的类上实现一个bithift操作(很容易,因为高long的乘数是2的幂).分裂移除除数直到它不比分红大; 将被移除的除数从被除数中减去并递增结果(适当的移位).重复.
再次编辑:你可能会发现BigInteger在这里做了你需要的.
将多头存储为分数.例如,BigDecimal money = new BigDecimal ("4.20")
成为long money = 420
.你只需要记住修改100来获得输出的美元和美分.如果你需要追踪十分之一美分,它就会变成现在long money = 4200
.
您可能想要转到定点数学.现在只是搜索一些图书馆.在sourceforge 定点上我还没有深入研究过这个问题.beartonics
你用org.jscience.economics.money测试过吗?因为这确保了准确性.固定点只能与分配给每个部分的位数一样准确,但速度很快.