double型数据不能进行精确计算

JAVA的double型数据不能进行精确计算,许多编程语言也是一样。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_java 科学计数法转换成数字

从这个例子可以看出计算的结果出现了误差,所以double型数据进行金额计算是不可靠的。

 

 四舍五入**(截断)

上面这个例子中,如果把计算结果按照指定的位数进行四舍五入,似乎就是我们想要的结果,但是在数学计算中四舍五入意味着误差,并且JDK中没有提供保留指定位数的四舍五入的API。

有一种方式是使用java.text.DecimalFormat类,它可以把数字格式化为指定的格式,但是它的舍入模式并不是我们数学上的四舍五入。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_数据_02

    从这个例子可以看到,DecimalFormat类把1.145保留两位小数之后是1.14而不是1.15,所以不能用DecimalFormat类对double型数据进行四舍五入。

 

科学记数法

double型数值在整数部分超过7时就自动转化为科学记数法表示,请看下面的例子:

java 科学计数法转换成数字 java double科学计数_java 科学计数法转换成数字_03

 

java.math.BigDecimal介绍

     BigDecimal的构造方法

BigDecimal是一个不变的、任意精度的有符号的十进制数对象。它提供了四个构造方法,其中有两个是用BigInteger构造的,在这里重点讲解用double和String构造的构造方法。

Constructor Summary

BigDecimal(double val)
          Translates a double into a BigDecimal.

BigDecimal(String val)
          Translates the String representation of a BigDecimal into a BigDecimal.

BigDecimal(double)是把一个double型十进制数构造为一个BigDecimal实例。

BigDecimal(String)是把一个以String表示的十进制数构造为BigDecimal实例。

通常情况下,对于浮点数我们都会定义为double类型,但是在JDK的API文档中有这样一段描述:BigDecimal(double)这个构造方法可能会有不可预知的结果。有人可能设想new BigDecimal(0.1)等于0.1,但它实际上是等于0.1000000000000000055511151231257827021181583404541015625,所以0.1不能用一个double型变量精确表示。因此,0.1被放进构造方法之后并不精确等于0.1,尽管外观看起来是相等的。

然而(String)构造方法则是完全可预知的,new BigDecimal(“0.1”)精确的等于0.1,因此,new BigDecimal(String)构造方法是被优先推荐使用的。

    请看下面的例子:

java 科学计数法转换成数字 java double科学计数_构造方法_04

    从这个例子可以看出,要进行精确的计算,必须使用BigDecimal(String val)

 

用BigDecimal进行四则运算

用BigDecimal可以进行精确的四则运算,请看下面三个例子:

java 科学计数法转换成数字 java double科学计数_四舍五入_05

java 科学计数法转换成数字 java double科学计数_构造方法_06

java 科学计数法转换成数字 java double科学计数_四舍五入_07

上面三个例子演示了用BigDecimal进行加法、减法、乘法运算,进行计算的数据与本文第一个例子是一样的,而计算的结果都是正确的。用BigDecimal进行除法运算时涉及到保留有效位数和舍入模式问题,这也是BigDecimal类使用的重点,在下面的章节介绍。

 

舍入模式

BigDecimal定义了以下舍入模式,只有在做除法运算四舍五入时才会用到舍入模式。下面的各小节的例子是应用各种舍入模式把double数值保留小数点后两位:

Field Summary

static int

ROUND_CEILING
          Rounding mode to round towards positive infinity.

static int

ROUND_DOWN
          Roun
ding 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.

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.

static int

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

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.

 

ROUND_CEILING

ROUND_CEILING模式是向正无穷大方向舍入。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_java 科学计数法转换成数字_08

   在数轴上,对于正数,如果数值与1.02之间的距离不到0.01就进位到1.02,所以除非数值等于1.01不用进位,否则所有大于1.01且小于1.02的数值都要进位到1.02,因为在数轴上1.02比1.01更靠近正无穷大;对于负数,所有小于等于-1.01且大于-1.02的数值都要舍弃从小数点后第三位开始的所有数字,因为在数轴上-1.01比-1.02更靠近正无穷大,这就是ROUND_CEILING模式——向正无穷大方向舍入。

 

ROUND_FLOOR

ROUND_FLOOR模式是向负无穷大方向舍入。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_java 科学计数法转换成数字_09

在数轴上,1.01比1.02更靠近负无穷大,所以,所有大于等于1.01且小于1.02的数值都要舍弃从小数点后第三位开始的所有数字;-1.02比-1.01更靠近负无穷大,所以,所有大于等于-1.02且小于-1.01的数值都要舍弃从小数点后第三位开始的所有数字,这就是ROUND_FLOOR模式——向靠近负无穷大的方向舍入。

 

ROUND_DOWN

ROUND_DOWN模式是向靠近零的方向舍入。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_四舍五入_10

    在数轴上,1.01比1.02更靠近零,所以,所有大于等于1.01且小于1.02的数值都要舍弃从小数点后第三位开始的所有数字;-1.01比-1.02更靠近零,所以,所有大于-1.02且小于等于-1.01的数值都要舍弃从小数点后第三位开始的所有数字,这就是ROUND_DOWN模式——向靠近零的方向舍入。

 

ROUND_UP

ROUND_UP模式是向远离零的方向舍入。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_构造方法_11

    ROUND_UP模式实际上对于正数就是向正无穷大方向舍入ROUND_CEILING,对于负数就是向负无穷大方向舍入ROUND_FLOOR,这就是ROUND_UP模式——向远离零的方向舍入。

 

ROUND_ UNNECESSARY

ROUND_ UNNECESSARY模式是不使用舍入模式。如果可以确保计算结果是精确的,则可以指定此模式,否则如果指定了使用此模式却遇到了不精确的计算结果,则抛出ArithmeticException。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_构造方法_12

    在不使用舍入模式的情况下,1.01和1.01000都可以精确的保留到小数点后第二位,而1.01001却不能,所以程序抛出了异常。

ROUND_HALF_DOWN

ROUND_HALF_DOWN模式是向距离最近的一边舍入,如果两边距离相等,就向靠近零的方向舍入。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_java 科学计数法转换成数字_13

在数轴上,1.01501与1.02之间的距离比1.01501与1.01之间的距离近,所以1.01501进位到1.02;1.01500和1.01之间的距离与1.01500和1.02之间的距离相等,而1.01比1.02更靠近零,所以1.01500保留两位小数为1.01,这就是ROUND_HALF_DOWN模式——向距离最近的一边舍入,如果两边距离相等,就向靠近零的方向舍入。

前面章节提到的java.text.DecimalFormat类使用的就是ROUND_HALF_DOWN模式。

 

(日常计算中使用)ROUND_HALF_UP

ROUND_HALF_UP模式是向距离最近的一边舍入,如果两边距离相等,就向远离零的方向舍入。请看下面的例子:

解释:一个数1.015如保留两2位小数,则舍入后是1.01或1.02,ROUND_HALF_UP此模式为向距离1.01和1.02近的方向舍;如果距离一样向0方向舍。便为1.02 。

java 科学计数法转换成数字 java double科学计数_四舍五入_14

在数轴上,1.01501与1.02之间的距离比1.01501与1.01之间的距离近,所以1.01501进位到1.02;1.01500和1.01之间的距离与1.01500和1.02之间的距离相等,而1.02比1.01更远离零,所以1.01500保留两位小数为1.02,这就是ROUND_HALF_UP模式——向距离最近的一边舍入,如果两边距离相等,就向远离零的方向舍入。

从例子中可以看出:ROUND_HALF_UP模式就是我们数学上的四舍五入。

 

ROUND_HALF_EVEN

ROUND_HALF_UP模式是向距离最近的一边舍入,如果两边距离相等且小数点后保留的位数是奇数,就使用ROUND_HALF_UP模式;如果两边距离相等且小数点后保留的位数是偶数,就使用ROUND_HALF_DOWN模式。请看下面的例子:

java 科学计数法转换成数字 java double科学计数_四舍五入_15

    在以上8种舍入模式中,我们日常计算只需要使用ROUND_HALF_UP模式。

 

用BigDecimal进行除法运算

java 科学计数法转换成数字 java double科学计数_数据_16

    从结果可以看出,虽然指定了保留小数点后10位,但由于151.3除以100的商可以精确到小数点后3位,所以b3的值就是1.513。

 

感谢项目经理的指导