性能主题

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错误。

下面是运算同样算术的性能比较:

double 0.018秒
no MathContext 4.1秒
MathContext.UNLIMITED 3.9秒
MathContext.DECIMAL32 4.2秒
MathContext.DECIMAL64 9.5秒
MathContext.DECIMAL128 13.9秒

最后,如果你想将一个浮点数转为字符串,好像很容易,实际相当困难,你需要知道的浮点数的二进制表示形式(IEEE-754)。详情将在sun.misc.floatingdecimal类的源代码看看(一般不存在JDK中,因此谷歌搜索它:)),特别是,一个很短的时差法,做了所有的魔术。自从1996年以后没有人敢碰它:)

下面是性能比较:


Double.toString(double) 4.1 sec

BigDecimal.toPlainString 12.4 sec

BigDecimal.toEngineeringString 12.5 sec


请注意:对于double, 请不要将double转为BigDecimal,先将double转为String,再将String转为BigDecimal。

如果你的算术需要将一个字符串作为输入,将其直接转换为BigDecimal。好处是你会避免任何的舍入误差。