开发过程中经常会需要进行一些数据的运算操作,比如加、减、乘、除,但是在计算过程中,到底使用哪种数据类型进行计算才是最合适的呢?
1、数据类型
1.1、 基础数据类型
- int:只能进行整数的计算,应用范围单一。
- float:单精度浮点型,可以进行小数类型的计算,但是容易丢失精度。
- double:双精度浮点型,精确度比float高,消耗的内存也比float高,但是也容易丢失精度。
- String:字符串类型,没法进行运算操作,但是恰恰要用到它和另外一种对象来进行数据的运算操作。
- BigDecimal :Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。
1.2、BigDecimal 详细介绍
以下是关于BigDecimal 的官方文档介绍,以及自己的一些总结。涉及到BigDecimal 以及和它相关联的MathContext、RoundingMode这两个类。
java.math.BigDecimal
- javase官方文档的描述:
不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。
BigDecimal 类提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换。toString() 方法提供 BigDecimal 的规范表示形式。
BigDecimal 类使用户能完全控制舍入行为。如果未指定舍入模式,并且无法表示准确结果,则抛出一个异常;否则,通过向该操作提供适当的 MathContext 对象,可以对已选择的精度和舍入模式执行计算。在任何情况下,可以为舍入控制提供八种舍入模式。使用此类(例如,ROUND_HALF_UP)中的整数字段来表示舍入模式已过时;应改为使用 RoundingMode enum(例如,RoundingMode.HALF_UP)的枚举值。
java.math.MathContext
- javase官方文档描述
该对象是封装上下文设置的不可变对象,它描述数字运算符的某些规则,例如由 BigDecimal 类实现的规则。
基本独立设置为:
1、precision:某个操作使用的数字个数;结果舍入到此精度
2、roundingMode:一个 RoundingMode 对象,该对象指定舍入使用的算法。
java.math.RoundingMode
- javase官方文档描述
为可能丢弃精度的数值操作指定一种舍入行为。每种舍入模式都指示如何计算返回舍入结果位数的最低有效位。如果返回的位数比表示精确数值结果所需的位数少,则舍弃的位数称为舍弃部分,而不管这些位数对数值的作用如何。换句话说,假设是一个数值,舍弃部分的绝对值可能大于 1。
- 这是一种枚举类型,以下是它的8种舍入方式的总结:
- UP
远离零方向舍入的舍入模式。
始终对非零舍弃部分前面的数字加 1。
注意,此舍入模式始终不会减少计算值的绝对值。
- DOWN
向零方向舍入的舍入模式。
从不对舍弃部分前面的数字加 1(即截尾)。
注意,此舍入模式始终不会增加计算值的绝对值。
- CEILING
向正无限大方向舍入的舍入模式。
如果结果为正,则舍入行为类似于 RoundingMode.UP;
如果结果为负,则舍入行为类似于 RoundingMode.DOWN。
注意,此舍入模式始终不会减少计算值。
- FLOOR
向负无限大方向舍入的舍入模式。
如果结果为正,则舍入行为类似于 RoundingMode.DOWN;
如果结果为负,则舍入行为类似于 RoundingMode.UP。
注意,此舍入模式始终不会增加计算值。
- HALF_UP
向最接近数字方向舍入的舍入模式。
如果与两个相邻数字的距离相等,则向上舍入。
如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;
否则舍入行为同 RoundingMode.DOWN。
注意,此舍入模式就是通常学校里讲的四舍五入。
- HALF_DOWN
向最接近数字方向舍入的舍入模式。
如果与两个相邻数字的距离相等,则向下舍入。
如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;
否则舍入行为同 RoundingMode.DOWN。
- HALF_EVEN
向最接近数字方向舍入的舍入模式。
如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;
如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN。
注意,在重复进行一系列计算时,此舍入模式可以在统计上将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。此舍入模式类似于 Java 中对 float 和 double 算法使用的舍入策略。
- UNNECESSARY
用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。
如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException。
- 来个表格看一下吧(这是javase官方api的图表)根据给定的舍入模式将输入数字舍入为一位数的结果:
输入数字 | UP | DOWN | CEILING | FLOOR | HALF_UP | HALF_DOWN | HALF_EVEN | UNNECESSARY |
---|---|---|---|---|---|---|---|---|
5.5 | 6 | 5 | 6 | 5 | 6 | 5 | 6 | 抛出 ArithmeticException |
2.5 | 3 | 2 | 3 | 2 | 3 | 2 | 2 | 抛出 ArithmeticException |
1.6 | 2 | 1 | 2 | 1 | 2 | 2 | 2 | 抛出 ArithmeticException |
1.1 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 抛出 ArithmeticException |
1.0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
-1.0 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
-1.1 | -2 | -1 | -1 | -2 | -1 | -1 | -1 | 抛出 ArithmeticException |
-1.6 | -2 | -1 | -1 | -2 | -2 | -2 | -2 | 抛出 ArithmeticException |
-2.5 | -3 | -2 | -2 | -3 | -3 | -2 | -2 | 抛出 ArithmeticException |
-5.5 | -6 | -5 | -5 | -6 | -6 | -5 | -6 | 抛出 ArithmeticException |
注意:以上8中枚举类型,在使用中需要特别注意,尤其是除法运算中的时候,需要制定枚举类型,切记,切记。
2、使用环境
在进行开发工作中,有时候会需要对一些数据进行加、减、乘、除的运算操作,这时候无论用int、float还是double进行计算,都会存在精度丢失的风险,这时候我们需要借助一个对象BigDecimal 来帮助我们进行计算。
BigDecimal 的构造方法有许多个,构造参数可以传int、float、double、String,但是经实际应用,使用构造参数为float或double类型的构造参数进行计算的时候,转换的BigDecimal 对象都会导致精度的丢失。通过实际验证,使用构造参数为String的构造方法可以避免精度丢失。
注意: 在以后需要进行加、减、乘、出计算的参数,都尽量用String来接收服务器返回的参数。
3、使用方式
以下是封装的用于进行计算的方法:
加法
/**
* 提供精确加法计算的add方法(例:1+2,1是被加数,2是加数)
*
* @param value1 被加数
* @param value2 加数
* @return 两个参数的和
*/
public static String add(String value1, String value2) {
BigDecimal b1 = new BigDecimal(value1);
BigDecimal b2 = new BigDecimal(value2);
return b1.add(b2).toString();
}
减法
/**
* 提供精确减法运算的sub方法(例:1-2,1是被减数,2是减数)
*
* @param value1 被减数
* @param value2 减数
* @return 两个参数的差
*/
public static String sub(String value1, String value2) {
BigDecimal b1 = new BigDecimal(value1);
BigDecimal b2 = new BigDecimal(value2);
return b1.subtract(b2).toString();
}
乘法
/**
* 提供精确乘法运算的mul方法(例:1*2,1是被乘数,2是乘数)
*
* @param value1 被乘数
* @param value2 乘数
* @return 两个参数的积
*/
public static String mul(String value1, String value2) {
BigDecimal b1 = new BigDecimal(value1);
BigDecimal b2 = new BigDecimal(value2);
return b1.multiply(b2).toString();
}
除法
/**
* 提供精确的除法运算方法div(例:1÷2,1是被除数,2是除数)
*
* @param value1 被除数
* @param value2 除数
* @param scale 精确范围
* @return 两个参数的商
* @throws IllegalAccessException
*/
public static String div(String value1, String value2, int scale) throws IllegalAccessException {
//如果精确范围小于0,抛出异常信息
if (scale < 0) {
throw new IllegalAccessException("精确度不能小于0");
}
BigDecimal b1 = new BigDecimal(value1);
BigDecimal b2 = new BigDecimal(value2);
//指定枚举类型,可以指定RoundingMode的8种枚举类型之一,对这8种枚举类型如果有疑问,可以参考上面RoundingMode的总结和描述。
//这里指定的DOWN,对应RoundingMode的ROUND_DOWN模式,会直接舍去精确度后面的数值。
return b1.divide(b2, scale, RoundingMode.DOWN).toString();
}
4、使用总结
用以上四个方法就可以应付开发过程中最基础的加、减、乘、除的运算了,但是在使用中要注意的情况:被减数和减数、被除数和除数的位置不要颠倒了,否则就会和预期的计算结果相差十万八千里了。