我为生活编写货币交易应用程序,所以我必须处理货币价值(遗憾的是,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慢.
有什么建议?
也许你应该开始用a = c/b替换a =(1/b)*c?这不是10倍,但仍然是一些东西.
如果我是你,我会创建自己的课程Money,这将保留很长的美元和长期美分,并在其中进行数学运算.
大多数双重操作可以提供足够的精度.你可以代表10万亿美元的精确度和双倍,这可能对你来说已经足够了.
在我所有的交易系统(四个不同的银行)中,他们使用了双倍的适当舍入.我没有看到任何使用BigDecimal的理由.
所以我原来的答案是错误的,因为我的基准写得很糟糕.我想我应该受到批评,而不是OP;)这可能是我写过的第一个基准之一......哦,这就是你学习的方式.而不是删除答案,这里是我没有测量错误的结果.一些说明:
预先计算数组,因此我不会通过生成它们来混淆结果
不要永远叫BigDecimal.doubleValue()
,因为它是极其缓慢
不要通过添加BigDecimal
s来搞乱结果.只返回一个值,并使用if语句来阻止编译器优化.但是,确保在大多数情况下让它工作以允许分支预测消除代码的这一部分.
测试:
BigDecimal:完全按照你的建议做数学运算
BigDecNoRecip:(1/b)*c = c/b,只做c/b
双倍:用双打做数学
这是输出:
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]); } }
假设您可以使用一些任意但已知的精度(比如十亿分之一)并且具有已知的最大值,您需要处理(一万亿亿美元?),您可以编写一个类,将该值存储为十亿分之一的整数一分钱.你需要两个长的代表它.这应该是使用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测试过吗?因为这确保了准确性.固定点只能与分配给每个部分的位数一样准确,但速度很快.