double型数据不能进行精确计算
JAVA的double型数据不能进行精确计算,许多编程语言也是一样。请看下面的例子:
从这个例子可以看出计算的结果出现了误差,所以用double型数据进行金额计算是不可靠的。
四舍五入**(截断)
上面这个例子中,如果把计算结果按照指定的位数进行四舍五入,似乎就是我们想要的结果,但是在数学计算中四舍五入意味着误差,并且JDK中没有提供保留指定位数的四舍五入的API。
有一种方式是使用java.text.DecimalFormat类,它可以把数字格式化为指定的格式,但是它的舍入模式并不是我们数学上的四舍五入。请看下面的例子:
从这个例子可以看到,DecimalFormat类把1.145保留两位小数之后是1.14而不是1.15,所以不能用DecimalFormat类对double型数据进行四舍五入。
科学记数法
double型数值在整数部分超过7位时就自动转化为科学记数法表示,请看下面的例子:
java.math.BigDecimal介绍
BigDecimal的构造方法
BigDecimal是一个不变的、任意精度的有符号的十进制数对象。它提供了四个构造方法,其中有两个是用BigInteger构造的,在这里重点讲解用double和String构造的构造方法。
Constructor Summary |
BigDecimal(double val) |
BigDecimal(String val) |
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)构造方法是被优先推荐使用的。
请看下面的例子:
从这个例子可以看出,要进行精确的计算,必须使用BigDecimal(String val)。
用BigDecimal进行四则运算
用BigDecimal可以进行精确的四则运算,请看下面三个例子:
上面三个例子演示了用BigDecimal进行加法、减法、乘法运算,进行计算的数据与本文第一个例子是一样的,而计算的结果都是正确的。用BigDecimal进行除法运算时涉及到保留有效位数和舍入模式问题,这也是BigDecimal类使用的重点,在下面的章节介绍。
舍入模式
BigDecimal定义了以下舍入模式,只有在做除法运算或四舍五入时才会用到舍入模式。下面的各小节的例子是应用各种舍入模式把double数值保留小数点后两位:
Field Summary | |
static int | ROUND_CEILING |
static int | ROUND_DOWN |
static int | ROUND_FLOOR |
static int | ROUND_HALF_DOWN |
static int | ROUND_HALF_EVEN |
static int | ROUND_HALF_UP |
static int | ROUND_UNNECESSARY |
static int | ROUND_UP |
ROUND_CEILING
ROUND_CEILING模式是向正无穷大方向舍入。请看下面的例子:
在数轴上,对于正数,如果数值与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模式是向负无穷大方向舍入。请看下面的例子:
在数轴上,1.01比1.02更靠近负无穷大,所以,所有大于等于1.01且小于1.02的数值都要舍弃从小数点后第三位开始的所有数字;-1.02比-1.01更靠近负无穷大,所以,所有大于等于-1.02且小于-1.01的数值都要舍弃从小数点后第三位开始的所有数字,这就是ROUND_FLOOR模式——向靠近负无穷大的方向舍入。
ROUND_DOWN
ROUND_DOWN模式是向靠近零的方向舍入。请看下面的例子:
在数轴上,1.01比1.02更靠近零,所以,所有大于等于1.01且小于1.02的数值都要舍弃从小数点后第三位开始的所有数字;-1.01比-1.02更靠近零,所以,所有大于-1.02且小于等于-1.01的数值都要舍弃从小数点后第三位开始的所有数字,这就是ROUND_DOWN模式——向靠近零的方向舍入。
ROUND_UP
ROUND_UP模式是向远离零的方向舍入。请看下面的例子:
ROUND_UP模式实际上对于正数就是向正无穷大方向舍入ROUND_CEILING,对于负数就是向负无穷大方向舍入ROUND_FLOOR,这就是ROUND_UP模式——向远离零的方向舍入。
ROUND_ UNNECESSARY
ROUND_ UNNECESSARY模式是不使用舍入模式。如果可以确保计算结果是精确的,则可以指定此模式,否则如果指定了使用此模式却遇到了不精确的计算结果,则抛出ArithmeticException。请看下面的例子:
在不使用舍入模式的情况下,1.01和1.01000都可以精确的保留到小数点后第二位,而1.01001却不能,所以程序抛出了异常。
ROUND_HALF_DOWN
ROUND_HALF_DOWN模式是向距离最近的一边舍入,如果两边距离相等,就向靠近零的方向舍入。请看下面的例子:
在数轴上,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 。
在数轴上,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模式。请看下面的例子:
在以上8种舍入模式中,我们日常计算只需要使用ROUND_HALF_UP模式。
用BigDecimal进行除法运算
从结果可以看出,虽然指定了保留小数点后10位,但由于151.3除以100的商可以精确到小数点后3位,所以b3的值就是1.513。
感谢项目经理的指导