Java处理小数点精度

Java处理小数点精度

简介

         java中提供了浮点数float和double类型表示小数计算,但是设计这2类型主要是为了科学计算和工程设计而设计的。因此,并没有提供完全精确的结果,所以不应该被用于需要精确结果的场合。特别是货币计算。

问题出现

         假设你现在有¥1.03元,花掉了¥0.42之后剩下多少钱呢?使用计算器是非常简单的计算,但是如果交给Java程序那结果如何呢?

System.out.println(1.03D - 0.42D);//结果:0.6100000000000001

假设你现在有¥1元,商店有系列糖果标价分别为:0.1,0.2,0.3......1元这么多种类。如果从0.1元的糖果开始购买一颗,如果剩下的钱还够就购买0.2元的糖果以此下去,直到钱不够为止。
double funds = 1.00D;
int itemsBought = 0;
for (double price = 0.10D; funds >= price; price += 0.10D) {
    funds -= price;
    ++itemsBought;
}
System.out.println("itemsBought:" + itemsBought);
System.out.println("funds:" + funds);
itemsBought:3
funds:0.3999999999999999

         如果程序运行正确那么itemsBought结果应该为4,funds结果应该为0。但是由于double的精度问题,导致结果出意外。

问题解决

BigDecimal TEN_CENTS = new BigDecimal("0.10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS;
     funds.compareTo(price) >= 0;
     price = price.add(TEN_CENTS)) {
    funds = funds.subtract(price);
    ++itemsBought;
}
System.out.println("itemsBought:" + itemsBought);
System.out.println("funds:" + funds);
itemsBought:4
funds:0.00

经过修改代码后正确的输出了结果。

BigDecimal类

    BigDecimal 类使用户能完全控制舍入行为。BigDecimal 类提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换。

         Java提供了BigDecimal、BigInteger2个类来处理精度小数点问题。这里只介绍BigDecimal类的用法和如何确定精度。

@Test
public void test4() {
    BigDecimal b1 = new BigDecimal(23.4470);
    BigDecimal s1 = b1.setScale(2, BigDecimal.ROUND_CEILING);
    System.out.println(s1);
}
//结果:23.45

构建Bigdecimal

BigDecimal类的初始化根据文档说明如下面这些方式:


         这么多种类的构造函数不需要全部去记住,只需要记住以下几个构造函数就可以了,毕竟开发过程中我们都是使用基本数据类型的,如果不能满足可以参考其他的构造函数去选择合适自己实际开发场景的。


@Test
public void test4() {
    BigDecimal i = new BigDecimal(100);//int类型
    BigDecimal l = new BigDecimal(100L);//long类型
    BigDecimal d = new BigDecimal(23.4470);//double类型
    BigDecimal s = new BigDecimal("23.4470");//string类型
}

创建初始值分别是0,1和10的BigDecimal。在BigDecimal中已经初始化好了这3个值的类型,如果需要可以直接使用下面的方式获取对象,不需要通过new的方式去初始化,或者可以通过静态的valueOf方法去创建。

确定精度

         创建了BigDecimal对象后,需要根据实际开发场景去确定精度是如何精确,例如保留多少位小数点,舍人的方式是向上还是向下等。


newScale参数:就是小数点保留多少位。

roundingMode参数:保留小数点时候采用舍入模式。

roundingMode参数

         BigDecimal提供了8种类型的舍入模式。这些类型都是舍入模式都是静态常亮。下面将详细介绍:

ROUND_UP类型

舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字。注意,此舍入模式始终不会减少计算值的大小。        

当BigDecimal的数值是小于0的时候,那么舍入就是远离0,例如:

BigDecimal d1 = new BigDecimal(-23.4470D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_UP);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0070(非零)。所以要增加数值。
//结果:-23.45
//在负数区间,-23.45是比-23.4470距离0坐标的位置更加远一点的。所以负数舍入模式是远离0的。

如果是下面的数值会怎样呢?
BigDecimal d1 = new BigDecimal(-23.4440D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_UP);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0040(非零)。所以要增加数值。
//结果:-23.45
//结果为什么不是-23.44而是-23.45呢?因为-23.44和-23.4440相比距离0坐标的位置更近,那么结果就舍入为-23.45

当BigDecimal的数值是大于0的时候,那么舍入也是远离0,例如:

BigDecimal d1 = new BigDecimal(23.4470D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_UP);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0070(非零)。所以要增加数值。
//结果:23.45
//在正数区间,23.45是比23.4470距离0坐标的位置更加远一点的。所有正数的舍入模式是远离0的。

如果是下面的数值会这样呢?
BigDecimal d1 = new BigDecimal(23.4440D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_UP);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0040(非零)。所以要增加数值。
//结果:23.45
//结果为什么不是23.44而是23.45呢?因为23.44和23.4440相比距离0坐标的位置更近,那么结果就舍入为23.45

这种舍入模式不是我们小时候数学的四舍五入的模式,所有要小心。总结这种舍入模式如果舍弃非零的数值:正数变大,负数变小。

ROUND_DOWN类型

接近零的舍入模式。在丢弃某部分之前始终不增加数字(即截短)。注意,此舍入模式始终不会增加计算值的大小。

当BigDecimal的值大于0的时候,舍入模式是接近0的。例如:

BigDecimal d1 = new BigDecimal(23.4430D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_DOWN);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0030(非零)。但是这模式丢弃之前不增加数值。
//结果:23.44
//在正数区间,由于23.44比23.4430更加接近0。所以舍入的最后值为23.44。

BigDecimal d1 = new BigDecimal(23.4480D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_DOWN);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0080(非零)。但是这模式丢弃之前不增加数值。
//结果:23.44
//23.4480如果按照小学数学的四舍五入方法去计算,结果应该是23.45。但是结果就是23.44。因为23.44比23.4480更接近0坐标。

当BigDecimal的值小于0的时候,舍入模式是接近0的。例如:

BigDecimal d1 = new BigDecimal(-23.4480D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_DOWN);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0080(非零)。但是这模式丢弃之前不增加数值。
//结果:-23.44
//在负数区间,由于-23.44比-23.4480更加接近0。

BigDecimal d1 = new BigDecimal(-23.4430D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_DOWN);
System.out.print(s1);
//保留2位小数点,舍弃的部分是0.0030(非零)。但是这模式丢弃之前不增加数值。
//结果:-23.44
//在负数区间,由于-23.44比-23.4430更加接近0坐标,所以结果是-23.44。

总结:这种模式是正数变小,负数变大。可以说是截取,就是截取指定的小数点。没有四舍五入的说法。

ROUND_CEILING类型

接近正无穷大的舍入模式。如果BigDecimal 为正,则舍入行为与ROUND_UP 相同;如果为负,则舍入行为与ROUND_DOWN 相同。注意,此舍入模式始终不会减少计算值。

当BIgDecimal的值大于0的时候,舍入模式是远离0的。例如:

BigDecimal d1 = new BigDecimal(23.4430D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_CEILING);
System.out.print(s1);
//保留2为小数,舍弃部分是0.0030。
//结果:23.45
//在正数区间,由于是远离0的。即使舍弃部分是0.0030。但是23.45比23.4430距离0坐标更加远。

BigDecimal d1 = new BigDecimal(23.4480D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_CEILING);
System.out.print(s1);
//保留2为小数,舍弃部分是0.0050。
//结果:23.45
//在正数区间,由于是远离0的。舍弃部分是0.0050。但是23.45比23.4480距离0坐标更加远。

当BIgDecimal的值小于0的时候,舍入模式是接近0的。例如:

BigDecimal d1 = new BigDecimal(-23.4480D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_CEILING);
System.out.print(s1);
//保留2为小数,舍弃部分是0.0080。
//结果:-23.44
//在负数区间,由于是接近0的。舍弃部分是0.0080。但是-23.44比-23.4480距离0坐标更加近。

BigDecimal d1 = new BigDecimal(-23.4430D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_CEILING);
System.out.print(s1);
//保留2为小数,舍弃部分是0.0030。
//结果:-23.44
//在负数区间,由于是接近0的。舍弃部分是0.0030。但是-23.44比-23.4430距离0坐标更加近。

总结:这种舍入模式正数就增大,负数也增大(-23.44是比-23.4430大的)。但是由于都是增大,对于正数来说比较麻烦,因为45.454按我们的计算四舍五入结果应是45.45。这模式计算结果是45.46。对于商品价格计算来说用户可能会觉得是欺诈。

ROUND_FLOOR类型

接近负无穷大的舍入模式。如果BigDecimal 为正,则舍入行为与ROUND_DOWN 相同;如果为负,则舍入行为与ROUND_UP 相同。注意,此舍入模式始终不会增加计算值。

         这类型和ROUND_CEILING是相反的。尝试自己参考ROUND_CEILING类型去总结去学习,这样对自己有好处。如果总计不出来的又想了解的可以留言,我来完善博文。

ROUND_HALF_UP类型(最重要)

         向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与ROUND_UP 相同;否则舍入行为与ROUND_DOWN 相同。注意,这是我们大多数人在小学时就学过的舍入模式。

无论正负数来讲,只要舍弃的部分>=0.5那么舍入也就是增加数值。否则就不增加。

BigDecimal d1 = new BigDecimal(45.454D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.print(s1);
//舍弃部分数值是0.004。4 < 5
//结果:45.45

BigDecimal d1 = new BigDecimal(45.456D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.print(s1);
//舍弃部分数值是0.006。6 >= 5
//结果:45.46

由于这舍入模式类型是我们小学学的四舍五人一样,这里就不过多说明,相信大家都可以理解。

ROUND_HALF_DOWN类型

         向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与ROUND_UP 相同;否则舍入行为与ROUND_DOWN 相同。

无论正负数来讲,只要舍弃的部分> 0.5(不包含等于5的情况)那么舍入也就是增加数值。否则就不增加。

BigDecimal d1 = new BigDecimal(45.455D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_HALF_DOWN);
System.out.print(s1);
//舍弃部分:0.005。5 > 5不成立,注意这模式是不包括等于5的情况的
//结果:45.45而不是45.46

BigDecimal d1 = new BigDecimal(45.456D);
BigDecimal s1 = d1.setScale(2, BigDecimal.ROUND_HALF_DOWN);
System.out.print(s1);
//舍弃部分:0.006。6 > 5成立,注意这模式是不包括等于5的情况的
//结果:45.46

这里就介绍了8种舍入模式的6种。个人觉得最重要的就是ROUND_HALF_UP类型了,因为这是我们小学时候学习的四舍五入模式。如果用于货币计算,那么这也是最公平的。如果说你老板比较扣,2224.244结果想多赚一分钱,就是2224.25的话。那就用ROUND_CEILING吧!

         BigDecimal类还提供了详细的加、减、绝对值、小数点位移等方法非常详细。这里只介绍小数点精度问题,想了解的可以去看API。

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读