[转]Java浮点数的精确计算及表示

原帖:http://www.bugcode.com/diary/10322

(1)、浮点数精确计算

每次报表统计的物资金额和实际的金额要差那么几分钱,和实际金额不一致,让客户觉得总是不那么舒服,原因是因为我们使用java的浮点类型double来定义物资金额,并且在报表统计中我们经常要进行一些运算,但Java中浮点数(double、float)的计算是非精确计算,请看下面一个例子:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->     System.out.println(0.05 + 0.01);
    System.out.println(1.0 - 0.42);
    System.out.println(4.015 * 100);
    System.out.println(123.3 / 100);

//你的期望输出是什么?可实际的输出确实这样的:

0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

(2)、四舍五入是否可以四舍五入呢?当然可以,习惯上我们本能就会这样考虑,但四舍五入意味着误差,商业运算中可能意味着错误,同时Java中也没有提供保留指定位数的四舍五入方法,只提供了一个Math.round(double d)和Math.round(float f)的方法,分别返回长整型和整型值。round方法不能设置保留几位小数,我们只能象这样(保留两位):
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public   double  round( double  value)
{
    
return  Math.round( value  *   100  )  /   100.0 ;
}

但非常不幸的是,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,如我们在上面看到的
4.015 * 100 = 401.49999999999994

因此如果我们要做到精确的四舍五入,这种方法不能满足我们的要求。

还有一种方式是使用java.text.DecimalFormat,但也存在问题,format采用的舍入模式是ROUND_HALF_DOWN(舍入模式在下面有介绍),比如说4.025保留两位小数会是4.02,因为.025距离” nearest neighbor”(.02和.03)长度是相等,向下舍入就是.02,如果是4.0251那么保留两位小数就是4.03。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> System.out.println( new  java.text.DecimalFormat( " 0.00 " ).format( 4.025 ));//输出是 4.02

System.out.println(
new  java.text.DecimalFormat( " 0.00 " ).format( 4.0251 ));//输出是 4.03

(3)、浮点数输出(科学记数法)
Java浮点型数值在大于9999999.0就自动转化为科学记数法来表示,我们看下面的例子:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->     System.out.println( 999999999.04 );

    System.out.println(
99999999.04 );

    System.out.println(
10000000.01 );

    System.out.println(
9999999.04 );

输出的结果如下:
9.9999999904E8
9.999999904E7
1.000000001E7
9999999.04
但有时我们可能不需要科学记数法的表示方法,需要转换为字符串,还不能直接用toString()等方法转换,很烦琐。

(4)、BigDecimal介绍

BigDecimal是Java提供的一个不变的、任意精度的有符号十进制数对象。它提供了四个构造器,有两个是用BigInteger构造,在这里我们不关心,我们重点看用double和String构造的两个构造器(有关BigInteger详细介绍请查阅j2se API文档)。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> BigDecimal( double  val)
Translates a 
double  into a BigDecimal.
BigDecimal(double)是把一个double类型十进制数构造为一个BigDecimal对象实例。

BigDecimal(String val)
Translates the String representation of a BigDecimal into a BigDecimal.
BigDecimal(String)是把一个以String表示的BigDecimal对象构造为BigDecimal对象实例。
习惯上,对于浮点数我们都会定义为double或float,但BigDecimal API文档中对于BigDecimal(double)有这么一段话:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> Note: the results of  this  constructor can be somewhat unpredictable. One might assume that  new  BigDecimal(. 1 ) is exactly equal to . 1 , but it is
actually equal to .
10000000000000000555111512312578   27021181583404541015625 . This is so because . 1  cannot be represented exactly as a  double
(or, 
for  that matter, as a binary fraction of any finite length). Thus, the  long  value that is being passed in to the constructor is not exactly
 equal to .
1 , appearances notwithstanding.

The (String) constructor, on the other hand, is perfectly predictable: 
new  BigDecimal( " .1 " ) is exactly equal to . 1 , as one would expect.
Therefore, it is generally recommended that the (String) constructor be used in preference to 
this  one
下面对这段话做简单解释:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 注意:这个构造器的结果可能会有不可预知的结果。有人可能设想new BigDecimal(. 1 )等于.1是正确的,但它实际上是等于. 1000000000000000055511151231257827021181583404541015625 ,这就是为什么.1不能用一个double精确表示的原因,因此,这个被放进构造器中的长值并不精确的等于. 1 ,尽管外观看起来是相等的。

然而(String)构造器,则完全可预知的,
new  BigDecimal(“. 1 ”)如同期望的那样精确的等于. 1 ,因此,(String)构造器是被优先推荐使用的。
看下面的结果:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->       System.out.println( new  BigDecimal( 123456789.02 ).toString());//输出为:123456789.01999999582767486572265625

      System.out.println(
new  BigDecimal( " 123456789.02 " ).toString());//输出为:123456789.02
现在我们知道,如果需要精确计算,非要用String来够造BigDecimal不可!
实现方案

现在我们已经知道怎么解决这个问题了,原则上是使用BigDecimal(String)构造器,我们建议,在商业应用开发中,涉及金额等浮点数计算的数据,全部定义为String,数据库中可定义为字符型字段,在需要使用这些数据进行运算的时候,使用BigDecimal(String)构造BigDecimal对象进行运算,保证数据的精确计算。同时避免了科学记数法的出现。如果科学记数表示法在应用中不是一种负担的话,可以考虑定义为浮点类型。这里我们提供了一个工具类,定义浮点数的加、减、乘、除和四舍五入等运算方法。以供参考。

源文件MathExtend.java:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> import  java.math.BigDecimal;

public   class  MathExtend

{

  
// 默认除法运算精度

  
private   static   final   int  DEFAULT_DIV_SCALE  =   10 ;

 

 
/**

  * 提供精确的加法运算。

  * 
@param  v1

  * 
@param  v2

  * 
@return  两个参数的和

  
*/

  
public   static   double  add( double  v1,  double  v2)

  {

      BigDecimal b1 
=   new  BigDecimal(Double.toString(v1));

      BigDecimal b2 
=   new  BigDecimal(Double.toString(v2));

      
return  b1.add(b2).doubleValue();

  }

  
/**

   * 提供精确的加法运算

   * 
@param  v1  

   * 
@param  v2

   * 
@return  两个参数数学加和,以字符串格式返回

   
*/

  
public   static  String add(String v1, String v2)

  {

      BigDecimal b1 
=   new  BigDecimal(v1);

      BigDecimal b2 
=   new  BigDecimal(v2);

      
return  b1.add(b2).toString();

  }

 

 
/**

  * 提供精确的减法运算。

  * 
@param  v1

  * 
@param  v2

  * 
@return  两个参数的差

  
*/

  
public   static   double  subtract( double  v1,  double  v2)

  {

      BigDecimal b1 
=   new  BigDecimal(Double.toString(v1));

      BigDecimal b2 
=   new  BigDecimal(Double.toString(v2));

      
return  b1.subtract(b2).doubleValue();

  }

 

  
/**

   * 提供精确的减法运算

   * 
@param  v1

   * 
@param  v2

   * 
@return  两个参数数学差,以字符串格式返回

   
*/

  
public   static  String subtract(String v1, String v2)

  {

      BigDecimal b1 
=   new  BigDecimal(v1);

      BigDecimal b2 
=   new  BigDecimal(v2);

      
return  b1.subtract(b2).toString();

  }

 

 

  
/**

  * 提供精确的乘法运算。

  * 
@param  v1

  * 
@param  v2

  * 
@return  两个参数的积

  
*/

  
public   static   double  multiply( double  v1,  double  v2)

  {

      BigDecimal b1 
=   new  BigDecimal(Double.toString(v1));

      BigDecimal b2 
=   new  BigDecimal(Double.toString(v2));

      
return  b1.multiply(b2).doubleValue();

  }

 

  
/**

   * 提供精确的乘法运算

   * 
@param  v1

   * 
@param  v2

   * 
@return  两个参数的数学积,以字符串格式返回

   
*/

  
public   static  String multiply(String v1, String v2)

  {

      BigDecimal b1 
=   new  BigDecimal(v1);

      BigDecimal b2 
=   new  BigDecimal(v2);

      
return  b1.multiply(b2).toString();

  }

 

  
/**

  * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到

  * 小数点以后10位,以后的数字四舍五入,舍入模式采用ROUND_HALF_EVEN

  * 
@param  v1

  * 
@param  v2

  * 
@return  两个参数的商

  
*/

  
public   static   double  divide( double  v1,  double  v2)

  {

      
return  divide(v1, v2, DEFAULT_DIV_SCALE);

  }

 

  
/**

   * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指

   * 定精度,以后的数字四舍五入。舍入模式采用ROUND_HALF_EVEN

   * 
@param  v1

   * 
@param  v2

   * 
@param  scale 表示需要精确到小数点以后几位。

   * 
@return  两个参数的商

   
*/

  
public   static   double  divide( double  v1, double  v2,  int  scale)

  {

      
return  divide(v1, v2, scale, BigDecimal.ROUND_HALF_EVEN);

  }

 

  
/**

   * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指

   * 定精度,以后的数字四舍五入。舍入模式采用用户指定舍入模式

   * 
@param  v1

   * 
@param  v2

   * 
@param  scale 表示需要精确到小数点以后几位

   * 
@param  round_mode 表示用户指定的舍入模式

   * 
@return  两个参数的商

   
*/

  
public   static   double  divide( double  v1, double  v2, int  scale,  int  round_mode){

          
if (scale  <   0 )

          {

              
throw   new  IllegalArgumentException( " The scale must be a positive integer or zero " );

          }

          BigDecimal b1 
=   new  BigDecimal(Double.toString(v1));

          BigDecimal b2 
=   new  BigDecimal(Double.toString(v2));

          
return  b1.divide(b2, scale, round_mode).doubleValue();

  }

 

  
/**

   * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到

   * 小数点以后10位,以后的数字四舍五入,舍入模式采用ROUND_HALF_EVEN

   * 
@param  v1

   * 
@param  v2

   * 
@return  两个参数的商,以字符串格式返回

   
*/

  
public   static  String divide(String v1, String v2)

  {

      
return  divide(v1, v2, DEFAULT_DIV_SCALE);

  }

 

  
/**

   * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指

   * 定精度,以后的数字四舍五入。舍入模式采用ROUND_HALF_EVEN

   * 
@param  v1

   * 
@param  v2

   * 
@param  scale 表示需要精确到小数点以后几位

   * 
@return  两个参数的商,以字符串格式返回

   
*/

  
public   static  String divide(String v1, String v2,  int  scale)

  {

      
return  divide(v1, v2, DEFAULT_DIV_SCALE, BigDecimal.ROUND_HALF_EVEN);

  }

 

  
/**

   * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指

   * 定精度,以后的数字四舍五入。舍入模式采用用户指定舍入模式

   * 
@param  v1

   * 
@param  v2

   * 
@param  scale 表示需要精确到小数点以后几位

   * 
@param  round_mode 表示用户指定的舍入模式

   * 
@return  两个参数的商,以字符串格式返回

   
*/

  
public   static  String divide(String v1, String v2,  int  scale,  int  round_mode)

  {

      
if (scale  <   0 )

      {

          
throw   new  IllegalArgumentException( " The scale must be a positive integer or zero " );

      }

      BigDecimal b1 
=   new  BigDecimal(v1);

      BigDecimal b2 
=   new  BigDecimal(v2);

      
return  b1.divide(b2, scale, round_mode).toString();

  }

 

  
/**

   * 提供精确的小数位四舍五入处理,舍入模式采用ROUND_HALF_EVEN

   * 
@param  v 需要四舍五入的数字

   * 
@param  scale 小数点后保留几位

   * 
@return  四舍五入后的结果

   
*/

  
public   static   double  round( double  v, int  scale)

  {

      
return  round(v, scale, BigDecimal.ROUND_HALF_EVEN);

  }

  
/**

   * 提供精确的小数位四舍五入处理

   * 
@param  v 需要四舍五入的数字

   * 
@param  scale 小数点后保留几位

   * 
@param  round_mode 指定的舍入模式

   * 
@return  四舍五入后的结果

   
*/

  
public   static   double  round( double  v,  int  scale,  int  round_mode)

  {

     
if (scale < 0 )

     {

         
throw   new  IllegalArgumentException( " The scale must be a positive integer or zero " );

     }

     BigDecimal b 
=   new  BigDecimal(Double.toString(v));

     
return  b.setScale(scale, round_mode).doubleValue();

  }

 

  
/**

   * 提供精确的小数位四舍五入处理,舍入模式采用ROUND_HALF_EVEN

   * 
@param  v 需要四舍五入的数字

   * 
@param  scale 小数点后保留几位

   * 
@return  四舍五入后的结果,以字符串格式返回

   
*/

  
public   static  String round(String v,  int  scale)

  {

    
return  round(v, scale, BigDecimal.ROUND_HALF_EVEN);

  }

  
/**

   * 提供精确的小数位四舍五入处理

   * 
@param  v 需要四舍五入的数字

   * 
@param  scale 小数点后保留几位

   * 
@param  round_mode 指定的舍入模式

   * 
@return  四舍五入后的结果,以字符串格式返回

   
*/

  
public   static  String round(String v,  int  scale,  int  round_mode)

  {

     
if (scale < 0 )

     {

         
throw   new  IllegalArgumentException( " The scale must be a positive integer or zero " );

     }

     BigDecimal b 
=   new  BigDecimal(v);

     
return  b.setScale(scale, round_mode).toString();

  }

}


(5)、BigDecimal 舍入模式(Rounding mode)介绍:
BigDecimal定义了一下舍入模式,只有在作除法运算或四舍五入时才用到舍入模式,下面简单介绍,详细请查阅J2se API文档
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> static   int
    

ROUND_CEILING

          Rounding mode to round towards positive infinity.

向正无穷方向舍入

static   int
    

ROUND_DOWN

          Rounding mode to round towards zero.

向零方向舍入

static   int
    

ROUND_FLOOR

          Rounding mode to round towards negative infinity.

向负无穷方向舍入

static   int
    

ROUND_HALF_DOWN

          Rounding mode to round towards 
" nearest neighbor "  unless both neighbors are equidistant, in which  case  round down.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.
55  保留一位小数结果为1. 5

static   int
    

ROUND_HALF_EVEN

          Rounding mode to round towards the 
" nearest neighbor "  unless both neighbors are equidistant, in which  case , round towards the even neighbor.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP ,如果是偶数,使用ROUND_HALF_DOWN

static   int
    

ROUND_HALF_UP

          Rounding mode to round towards 
" nearest neighbor "  unless both neighbors are equidistant, in which  case  round up.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 
1 .55保留一位小数结果为1. 6

static   int
    

ROUND_UNNECESSARY

          Rounding mode to 
assert  that the requested operation has an exact result, hence no rounding is necessary.

计算结果是精确的,不需要舍入模式

static   int
    

ROUND_UP

          Rounding mode to round away from zero.

向远离0的方向舍入
小示例:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> // 如何判断一个Double数如果有小数为的话保留相应的位数,如果无小数位则返回整数?
/*
例如:public String round(double v,int num){
}
v是要计算的数,num是保留的位数
例如传:12.123,2 则返回12.12 传12 则返回12
传12.123,3 则返回12.123 谢谢!
*/

import  java.text. * ;
import  java.math. * ;
public   class  DoubleFormat
{
 
public   static   void  main(String[]args)
 {
  DoubleFormat t 
=   new  DoubleFormat();
  System.out.print(t.doubleOutPut(
12.345 , 2 ));
  
// System.out.print(t.round(12.335,2));
 }
 
public  String doubleOutPut( double  v,Integer num)
 {
  
if  (v  ==  Double.valueOf(v).intValue())
  
return  Double.valueOf(v).intValue() + "" ;
  
else
  {
   
/*  下面这段所用的舍入模式是ROUND_HALF_EVEN
   NumberFormat formatter = NumberFormat.getNumberInstance();
   formatter.setMaximumFractionDigits(num);
   formatter.setMinimumFractionDigits(num);
   return formatter.format(v);
   
*/
   
   
// 下面的舍入模式是  ROUND_HALF_UP
     BigDecimal b  =   new  BigDecimal(Double.toString(v));

                          
return  b.setScale(num,BigDecimal.ROUND_HALF_UP).toString();
  }
  
 }
 
/* 这是网友的另外的一种实现方法。我未测试
 public String round(double v,int num){
         String fmt = "0000000000000000";//16位
         fmt = num>0?"0."+fmt.substring(0,num):"0";
        DecimalFormat df = new DecimalFormat(fmt);
         return(df.format(v));
    
*/
}
  
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值