【问题】在项目中,我们进行计算的时候,有时候需要考虑 四舍五入,精度丢失的问题,面对这种问题,我们应该怎么处理?
System.out.println(0.2+0.1);
System.out.println(0.3-0.1);
System.out.println(0.4*0.1);
System.out.println(0.2+1);
输出结果:
请忽略 浮点型 与整数的计算。
我们看到上面三个测试 浮点型与浮点型四则运算,运算结果并不是准确的,也不是我们所想要的。
造成这种现象的原因:
因为不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。
注:根本原因是:十进制值通常没有完全相同的二进制表示形式;十进制数的二进制表示形式可能不精确。只能无限接近于那个值。
另外注意一点,整数与浮点数计算,或 整数向浮点数转换的时候,会向下转型。
【怎么解决】
1、简介
java提供了 BigDecimal这个api,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
2、构造器:
BigDecimal(int) 创建一个具有参数所指定整数值的对象。
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 //不推荐使用
BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。 推荐使用
这里注意一个问题,对于double 类型的数据,最好不要直接传入BigDecimal构造函数,创建 BigDecimal对象,会出现精度问题,计算错误,建议将double转换成String
比如:
3、方法描述
add(BigDecimal) BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。
multiply(BigDecimal) BigDecimal对象中的值相乘,然后返回这个对象。
divide(BigDecimal) BigDecimal对象中的值相除,然后返回这个对象。
toString() 将BigDecimal对象的数值转换成字符串。
doubleValue() 将BigDecimal对象中的值以双精度数返回。
floatValue() 将BigDecimal对象中的值以单精度数返回。
longValue() 将BigDecimal对象中的值以长整数返回。
intValue() 将BigDecimal对象中的值以整数返回
在实战中,比较实用的几个方法
stripTrailingZeros():去除尾部所有的0,并返回一个BigDecimal类型的数据,不能保证不是科学计数法。
后加toString()把BigDecimal类型的数据转化成String类型数据,但还是不能保证不是科学计数法。
后加toPlainString()把BigDecimal类型的数据转化成String类型数据,并保证不是科学计数法。
比如上边的例子中,我想去掉最后 1.2000的零
除了上述的四则运算,和值输出以外,BigDecimal 更常用的是 取一个数的某一部分,可以是取整数,也可以规定保留的精度
setScale(保留小数点后几位,舍入方法)
1、UP(BigDecimal.ROUND_UP):远离零方向舍入。向远离0的方向舍入,也就是说,向绝对值最大的方向舍入,只要舍弃位非0即进位。
2、DOWN(BigDecimal.ROUND_DOWN):趋向0方向舍入。向0方向靠拢,也就是说,向绝对值最小的方向输入,注意:所有的位都舍弃,不存在进位情况。
3、CEILING(BigDecimal.ROUND_CEILING):向正无穷方向舍入。向正最大方向靠拢,如果是正数,舍入行为类似于ROUND_UP;如果为负数,则舍入行为类似于ROUND_DOWN.注意:Math.round方法使用的即为此模式。
4、FLOOR(BigDecimal.ROUND_FLOOR):向负无穷方向舍入。向负无穷方向靠拢,如果是正数,则舍入行为类似ROUND_DOWN,如果是负数,舍入行为类似以ROUND_UP。
5、HALF_UP(BigDecimal.ROUND_HALF_UP):最近数字舍入(5舍),这就是我们经典的四舍五入。
6、HALF_DOWN(BigDecimal.ROUND_HALF_DOWN):最近数字舍入(5舍)。在四舍五入中,5是进位的,在HALF_DOWN中却是舍弃不进位。
5.5 -> 5
2.5 -> 2
1.6 -> 2
1.1 -> 1
1.0 -> 1
-1.0 -> -1
-1.1 -> -1
-1.6 -> -2
-2.5 -> -2
-5.5 -> -5
7、HALF_EVEN(BigDecimal.ROUND_HALF_EVEN):银行家算法。四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。
5.5 -> 6
2.5 -> 2
1.6 -> 2
1.1 -> 1
1.0 -> 1
-1.0 -> -1
-1.1 -> -1
-1.6 -> -2
-2.5 -> -2
-5.5 -> -6
8、UNNECESSARY(BigDecimal.ROUND_UNNECESSARY):断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
精确到一位小数点
1.0—>1
1.1抛异常
参考链接:https://blog.csdn.net/xnkjdx105/article/details/124213231
所以,我们在面对一般、甚至精确要求非常高的情况下,对小数位进行操作的时候,都可以考虑 BigDecimal
特别是求一个浮点数,保留其后xx位小数的时候,最为可靠。
1)把浮点数转为String //防止精度丢失
2)调用new BigDecimal(String d),将浮点型转换成更为精确的BigDecimal对象
3) 使用BigDecimal的setScale 方法,求得所需要的精确的结果
对于整形运算,我们为了防止它的结果向下转型,也可以考虑使用Bigdecimal 比如:
我们日常随意的书写,会造成各种类型转换的隐患,
输出结果:
//TODO