Java算术运算性能设计要点
如果想进行快速安全的金融财务有关的算术计算,也就是浮点的加减乘除算术运算,请遵循下列条件:
首先,不要使用float进行任何算术运算,因为它的精度太低只有32位。double也不是很精确,看下面案例:
System.out.println( "362.2 - 362.6 = " + ( 362.2 - 362.6 ) );
结果是"362.2 - 362.6 = -0.4000000000000341"
当使用double避免与非整数值一起工作,使用long进行加减算法,进行除法和乘法请使用Math.round/rint/ceil/floor。
再看看double和BigDecimal比较,让我们用1.5% 乘362.2$ 计算,运行100M次:
BigDecima:
int res = 0;
final BigDecimal orig = new BigDecimal( "362.2" );
final BigDecimal mult = new BigDecimal( "0.015" ); //1.5%
for ( int i = 0; i < ITERS; ++i )
{
final BigDecimal result = orig.multiply( mult, MathContext.DECIMAL64 );
if ( result != null ) res++;
}
double:
final double orig = 36220; //362.2 in cents
final double mult = 0.015; //1.5%
for ( int i = 0; i < ITERS; ++i )
{
final long result = Math.round( orig * mult );
if ( result != 543 ) res++; //543.3 cents actually
}
测试下来,BigDecimal使用了4.899秒,而double使用了0.58秒。如果你的计算正好是52位(double的精确度),尽量使用double,而不是BigDecimal.
如果你仍然想使用BigDecimal作为您的货币计算(很方便,至少!),下面的文字会给你正确的使用BigDecimal的一些提示。
对于BigDecimals您可以同时指定舍入模式和精度,但有一个更方便的方法 - 您可以使用MathContext代替,其中包含精度和舍入的信息。更重要的是,也有一些预定义的,这可以让你模拟float/double/decimal_128算术运算,没有任何四舍五入问题:MathContext.DECIMAL32/DECIMAL64/DECIMAL128。
MathContext.UNLIMITED是一个缺省的MathContext值。但是不要使用它,它等同于一点没有上下文。
你可以使用MathContext进行加减运算,但是对于乘除最好规定一个DECIMAL*上下文,它们是必需的,因为当运算结果有一个无限长的十进制扩展,这些操作需要指定精度。否则会报ArithmeticException错误。
下面是运算同样算术的性能比较:
double0.018秒
no MathContext4.1秒
MathContext.UNLIMITED3.9秒
MathContext.DECIMAL324.2秒
MathContext.DECIMAL649.5秒
MathContext.DECIMAL12813.9秒
最后,如果你想将一个浮点数转为字符串,好像很容易,实际相当困难,你需要知道的浮点数的二进制表示形式(IEEE-754)。详情将在sun.misc.floatingdecimal类的源代码看看(一般不存在JDK中,因此谷歌搜索它:)),特别是,一个很短的时差法,做了所有的魔术。自从1996年以后没有人敢碰它:)
下面是性能比较:
Double.toString(double)4.1 sec
BigDecimal.toPlainString12.4 sec
BigDecimal.toEngineeringString 12.5 sec
请注意:对于double, 请不要将double转为BigDecimal,先将double转为String,再将String转为BigDecimal。
如果你的算术需要将一个字符串作为输入,将其直接转换为BigDecimal。好处是你会避免任何的舍入误差。