# 使用 double/long 和 BigDecimal 进行货币计算

## 使用 long/double 进行货币操作

### 如何正确使用？

double计算也不精确，即使是简单的加减运算：

``````System.out.println( "362.2 - 362.6 = " + ( 362.2 - 362.6 ) );
``````

1. 在最小的货币单位计算中，避免使用非整数的double值。
2. 根据系统要求，使用Math.round/rint/ceil/floor对乘法/除法计算结果进行取整操作。

PS：只要能遵守上面的两条建议，还是能够使用long/double数据类型进行加减运算的。

### 什么情况下使用？

``````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++;
}
``````

``````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
}
``````

``````final double orig = 36220; //362.2 in cents
for ( long i = 0; i < ITERS; ++i )
{
final long result = Math.round( orig * i );
if ( result != 543 ) res++;    //compare with something
}
``````

## 使用 BigDecimal 进行货币操作

### 如何正确使用?

``````final BigDecimal three = new BigDecimal( "3" );
try
{
System.out.println( BigDecimal.ONE.divide( three ) );
}
catch ( ArithmeticException ex )
{
System.out.println( "Got an exception while calculating 1/3 : " + ex.getMessage() );
}
``````

### BigDecimal 性能如何？

``````BigDecimal res = BigDecimal.ZERO;
final BigDecimal a = new BigDecimal( Math.E, context );
final BigDecimal b = new BigDecimal( Math.E, context );
final BigDecimal c = new BigDecimal( Math.E, context );
for ( int i = 0; i < 10000000; ++i )
{
final BigDecimal val = a.multiply( b, context ).add( c, context );
res = res.add( val, context );
}
``````

double 0.018Sec 1.010733792587689E8
noMathContext 4.1sec 101073379.273896945320908905278183855697464192452494578591950602844407036684515333035960793495178222656250000000
MathContext.UNLIMITED 3.9sec 101073379.273896945320908905278183855697464192452494578591950602844407036684515333035960793495178222656250000000
MathContext.DECIMAL32 4.2sec 100000000
MathContext.DECIMAL64 9.5sec 101073379.2938854
MathContext.DECIMAL128 13.9sec 101073379.2738969453209089052948157

## 数值转字符串

1、Java6转换Double对象到String需要经过一系列调用：

```  Double
public String toString() {
return String.valueOf(value);
}

String
public static String valueOf(double d) {
return Double.toString(d);
}

Double
public static String toString(double d) {
return new FloatingDecimal(d).toJavaFormatString();
}
```

2、Java 7 中就非常直接：

```jdk 7 Double
public String toString() {
}
```

3、BigDecimal 转换为 String

BigDecimal 有３个用于转换为String的方法：toString，toPlainString和toEngineeringString。toString会缓存toEngineeringString的结果，用于后续的调用(这样做可能是因为BigDecimal的值是不可变的)。下面测试了将Math.E转为字符串10M次耗时：

Double.toString(double) BigDecimal.toPlainString BigDecimal.toEngineeringString
4.1 sec 12.4 sec 12.5 sec

## 总结

1. 将货币值以最小的货币单位(比如分)存放到long类型变量中。
2. 如果以最小的货币单位计算时使用了double类型，要避免产生非整数的值，否则会产生精度问题。
3. 使用long类型进行加减运算。
4. 根据系统需求，使用Math.round/rint/ceil/floor对乘除结果进行取整。
5. 计算要满足double精度（52位）。

JSmiles

2583 文章
29 评论
84935 人气