`

[转]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));
    
*/
}
  
}


分享到:
评论

相关推荐

    java精确计算浮点数工具类

    Java对浮点数的计算是不精确的,比如0.05+0.01结果不是0.06,而是0.060000000000000005,更有甚者,一个数除以0.0,Java是不会抛异常,而是得出无穷大的结果.本工具类解决了上述问题.该类提供了加减乘除四则运算的精确计算...

    Java中实现浮点数的精确计算

    ### Java中实现浮点数的精确计算 在Java编程中,使用`float`和`double`类型进行浮点数计算时经常会遇到精度丢失的问题。这是因为浮点数在计算机内部是以二进制形式存储的,而某些十进制小数无法用二进制精确表示,...

    Java实现的浮点类的精确计算

    总结一下,Java中的浮点数精确计算主要是通过`java.math.BigDecimal`类来实现的,而"Java实现的浮点类的精确计算"则可能是一个自定义工具类,封装了`BigDecimal`的操作,以提供方便、准确的浮点数计算服务。...

    解决javascript中的浮点数计算不精确问题

    在JavaScript编程中,浮点数计算不精确是一个常见的问题,源于其内部的二进制浮点数表示方式。本文将深入探讨这个问题,并提供解决方案。首先,我们要理解为什么会出现这种不精确性。 JavaScript遵循IEEE 754标准来...

    HEX与浮点数相互转换

    HEX(十六进制)是计算机中广泛使用的数字表示方式,尤其在内存和CPU指令中,而浮点数则用于表示带有小数部分的数值,常用于科学计算和精确度要求较高的场景。转换这两者之间的形式对于理解和调试程序、存储数据以及...

    JAVA中浮点数的运算

    在Java编程语言中,浮点数的运算涉及到的是数值计算,特别是小数部分的处理。浮点数在计算机内部是以二进制表示的,这与我们通常使用的十进制系统不同,因此在进行浮点数运算时可能会出现精度丢失的问题。这篇博客...

    IEEE754浮点数_数值转换_浮点数转换_

    这个标准对于数字计算的精确性和可移植性至关重要,广泛应用于现代计算机系统、编程语言以及硬件处理器。在本篇文章中,我们将深入探讨IEEE754浮点数的结构、数值转换以及如何在不同表示之间进行转换。 首先,让...

    Java浮点数.pdf

    总的来说,Java中的浮点数类型提供了对实数的高效存储和处理,但因为舍入误差和非均匀的间隔,可能不适合需要精确计算的场合。理解浮点数的存储和工作原理对于编写高性能和正确性的代码至关重要。

    浮点数与十进制数转换工具

    首先,浮点数是一种表示实数的方式,特别适合于科学计算,因为它可以表示广泛的数值范围,并且精度相对较高。在计算机内部,浮点数通常按照IEEE 754标准存储。这个标准定义了两种主要的格式:单精度(32位)和双精度...

    疯狂Java讲义 浮点数转换成人民币读法

    由于浮点数在计算机内部是以二进制形式存储的,因此可能会存在一些无法精确表示的小数,这会导致计算结果出现细微的误差。 在进行转换时,我们需要将浮点数分解为整数部分和小数部分。Java提供了Math.floor()函数...

    [Java]精确表达浮点数

    如果你希望得到精确计算结果,最好是用分数形式来表示小数。有限小数或者无限循环小数都可以转化为分数。比如: 0.9=9/10 0.33(3)=1/3=3/9 给定一个有限小数或者无限循环小数,你能否以分母最小的分数形式返回这个...

    浮点数计算为什么会出错

    对于那些需要更高精度的计算,比如金融计算,通常推荐使用特定的数值类型,例如Java中的BigDecimal,它不依赖于IEEE 754标准的浮点数表示,而是以任意精度来表示小数,这虽然会牺牲一些性能,但能提供更精确的结果。...

    Java中如何正确进行浮点数运算

    总之,理解和掌握Java中的浮点数存储和运算特性,以及如何利用`BigDecimal`进行精确计算,是每个Java开发者必备的知识点。在进行浮点数运算时,务必考虑精度问题,并根据实际情况选择合适的方法,以避免潜在的错误和...

    js浮点数处理转精确值

    但在编写Web应用时,我们可能需要在服务器端使用Quartz来调度执行一些需要精确计算的任务,比如定期更新数据库中的浮点数数据。 对于需要高精度计算的场景,JavaScript社区也提供了许多库,如`decimal.js`或`big...

    Java 精确计算-double-float-String

    标题中的"Java 精确计算 - double-float-String"指向的是Java中处理浮点数(double和float)以及字符串表示的数值时可能遇到的精度问题,以及如何通过特定方法实现精确计算。描述中提到的链接指向了一个具体的博客...

    16进制单精度(32位)浮点型转换器源码

    浮点型(Floating-Point)数据是用于表示带有小数部分的数字,通常用于科学计算和工程应用,因为它们可以表示很大的数值范围,但精确度相对较低。 单精度浮点数在IEEE 754标准中定义,占据32位,这32位分为三部分:...

    算法-计算浮点数相除的余(信息学奥赛一本通-T1029)(包含源程序).rar

    这个题目可能是为了帮助参赛者理解浮点数的精确表示以及如何进行浮点数的除法操作。 浮点数是计算机中用来表示实数的一种方式,由于计算机内存有限,不能无限制地存储每一个小数位,因此它采用了科学记数法的表示...

    浮点数转四字节数HexToByte

    浮点数是一种用于表示数值的格式,它包括正负号、指数和尾数部分,能够精确表示大部分实数。在计算机内部,浮点数通常按照IEEE 754标准进行存储,该标准定义了不同的浮点数精度,如单精度(32位)和双精度(64位)。...

    解析Java中的精确计算方法.pdf

    总之,虽然Java的浮点数类型在日常编程中方便、易用,但在要求精确计算的领域,开发者必须意识到它们的局限性,并采用Decimal和BigDecimal类型来确保数值计算的精确无误。通过合理使用这些高精度数值类型,并掌握其...

    JAVA浮点数计算精度损失底层原理与解决方案

    浮点数计算在Java中常常会遇到精度损失的问题,这是由于计算机硬件的限制以及浮点数在二进制表示上的特性所导致的。在Java中,浮点数类型包括单精度浮点数`float`(4字节,32位)和双精度浮点数`double`(8字节,64...

Global site tag (gtag.js) - Google Analytics