BigDecimal
float 和 double 同样也是可以表示浮点数,为啥在对于要求精确的进度计算时,尤其是关于币值相关,都采用 BigDecimal 类型来处理?
- float 和 double 类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的 快速近似 计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该用于要求精确结果的场合。——《Effective Java》
- float 精度 7 位,double 精度 16 位
综上所述,对于精确计算时,就不能采用 float 和 double 来计算了,而是 正确的使用 BigDecimal 时,结果才是精确的,为什么会这么说,那就跟着我一块来深入了解 BigDecimal,我们查看java.math
路径下,除了 BigDecimal 还有 BigInteger ,因此,我们先去了解 BigInteger
BigInteger
Java 中,由 CPU 原生提供的整形最大范围是 64 位long
类型整数。使用long
类型整数可以直接通过 CPU 指令进行计算,速度非常快。如果使用的整数范围超过了long
类型的范围怎么办?这时就只能用软件来模拟一个大整数。BigInteger表示不可变的任意精度的整数(继承Number
)。BigInteger 内部用一个 int[]
数组来模拟一个非常大的整数
构造方法
- BigInteger(byte[] val):将包含 BigInteger 的二进制补码表示形式的 byte 数组转换为 BigInteger
- BigInteger(int signum, byte[] magnitude):将 BigInteger 的符号-数量表示形式转换为 BigInteger。
- BigInteger(int bitLength, int certainty, Random rnd):构造一个随机生成的正 BigInteger,它可能是一个具有指定 bitLength 的素数
- BigInteger(int numBits, Random rnd):构造一个随机生成的 BigInteger,它是在 0 到 (2numBits - 1)(包括)范围内均匀分布的值
- BigInteger(String val):将 BigInteger 的十进制字符串表示形式转换为 BigInteger,常用构造方法
- BigInteger(String val, int radix):将指定基数的 BigInteger 的字符串表示形式转换为 BigInteger
1 | BigInteger bi = new BigInteger("1234567890"); |
常用运算方法
对于加减乘除等运算,BigInteger 提供了对应的方法
1 | BigInteger a = new BigInteger("1234567890"); |
转换
和 long
类型整数运算比,BigInteger
不会有范围限制,但缺点是速度比较慢。
1 | BigInteger i = new BigInteger("123456789000"); |
使用
longValueExact()
方法时,如果超出了long
类型的范围,会抛出ArithmeticException
BigInteger
和 Integer
、 Long
一样,也是不可变类,并且也继承自 Number
类。因为 Number
定义了转换为基本类型的几个方法:
- 转换为
byte
:byteValue()
- 转换为
short
:shortValue()
- 转换为
int
:intValue()
- 转换为
long
:longValue()
- 转换为
float
:floatValue()
- 转换为
double
:doubleValue()
通过上述方法,可以把 BigInteger
转换为基本类型。如果 BigInteger
表示的范围超过了基本类型,转换时将丢失高位信息,即结果不一定准确;因此,如果需要 准确的转换成基本类型,可以使用 intValueExact()
、longValueExact()
等方法,在转换时如果超出范围,将直接抛出 ArithmeticException
异常
BigDecimal
BigDecimal
与 BigInteger
类似,BigDecimal
表示一个任意大小且精度完全准确的浮点数。BigDecimal 是由任意精度的整数非标度值(unscaled value)和 32 位的整数标度(scale)组成,通常用于币值的计算。
构造方法
BigDecimal 拥有16 个构造方法,常用如下三种
- BigDecimal BigDecimal(double d); // 不允许使用,精度不能保证
- BigDecimal BigDecimal(String s); // 常用,推荐使用
- static BigDecimal valueOf(double d); // 常用,推荐使用
1 | BigDecimal bigDecimal = new BigDecimal(2); |
- 参数类型为 double 的构造方法的结果有一定的不可预知性;
- 参数类型为 String 的构造方法的结果是完全可预知的,因此我们在编写时尽量都用 String 的构造方法
- 当 double 必须用作 BigDecimal 的源时可以用 BigDecimal 的静态方法 value()
常用方法
1 | BigDecimal a = new BigDecimal("1234567890.56789"); |
转换
与BigInteger
相同
舍入模式
- ROUND_CEILING:向 正无限大方向舍入 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN
1
2
3
4
5
6
7
8
9
10
11
125.5 => 6
1.1 => 2
-1.0 => -1
-2.5 => -2
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.0");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("ROUND_CEILING模式:" + a1.setScale(0, RoundingMode.CEILING));
System.out.println("ROUND_CEILING模式:" + a2.setScale(0, RoundingMode.CEILING));
System.out.println("ROUND_CEILING模式:" + a3.setScale(0, RoundingMode.CEILING));
System.out.println("ROUND_CEILING模式:" + a4.setScale(0, RoundingMode.CEILING)); - RoundingMode.DOWN:向 零方向舍入 的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值
1
2
3
4
5
6
7
8
9
10
11
125.5 => 5
1.1 => 1
-1.0 => -1
-2.5 => -2
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.0");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("DOWN模式:" + a1.setScale(0, RoundingMode.DOWN));
System.out.println("DOWN模式:" + a2.setScale(0, RoundingMode.DOWN));
System.out.println("DOWN模式:" + a3.setScale(0, RoundingMode.DOWN));
System.out.println("DOWN模式:" + a4.setScale(0, RoundingMode.DOWN)); - RoundingMode.FLOOR(此舍入模式始终不会增加计算值):向 负无限大方向舍入 的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于 RoundingMode.UP
1
2
3
4
5
6
7
8
9
10
11
125.5 => 5
1.1 => 1
-1.0 => -1
-2.5 => -3
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.0");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("FLOOR模式:" + a1.setScale(0, RoundingMode.FLOOR));
System.out.println("FLOOR模式:" + a2.setScale(0, RoundingMode.FLOOR));
System.out.println("FLOOR模式:" + a3.setScale(0, RoundingMode.FLOOR));
System.out.println("FLOOR模式:" + a4.setScale(0, RoundingMode.FLOOR)); - RoundingMode.HALF_DOWN:向 最接近数字方向舍入 的舍入模式,如果与两个相邻数字的距离相等,则向 下舍入 。如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
1
2
3
4
5
6
7
8
9
10
11
125.5 => 5
1.1 => 1
-1.1 => -1
-2.5 => -2
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.0");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("HALF_DOWN模式:" + a1.setScale(0, RoundingMode.HALF_DOWN));
System.out.println("HALF_DOWN模式:" + a2.setScale(0, RoundingMode.HALF_DOWN));
System.out.println("HALF_DOWN模式:" + a3.setScale(0, RoundingMode.HALF_DOWN));
System.out.println("HALF_DOWN模式:" + a4.setScale(0, RoundingMode.HALF_DOWN)); - RoundingMode.HALF_EVEN:向 最接近数字方向舍入 的舍入模式,如果与两个相邻数字的距离相等,则向 相邻的偶数舍入 。如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN
1
2
3
4
5
6
7
8
9
10
11
125.5 => 6
1.1 => 1
-1.0 => -1
-2.5 => -2
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.0");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("HALF_EVEN模式:" + a1.setScale(0, RoundingMode.HALF_EVEN));
System.out.println("HALF_EVEN模式:" + a2.setScale(0, RoundingMode.HALF_EVEN));
System.out.println("HALF_EVEN模式:" + a3.setScale(0, RoundingMode.HALF_EVEN));
System.out.println("HALF_EVEN模式:" + a4.setScale(0, RoundingMode.HALF_EVEN)); - RoundingMode.HALF_UP(此舍入模式就是通常学校里讲的四舍五入):向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
1
2
3
4
5
6
7
8
9
10
11
125.5 => 6
1.1 => 1
-1.1 => -1
-2.5 => -3
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.1");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("HALF_UP模式:" + a1.setScale(0, RoundingMode.HALF_UP));
System.out.println("HALF_UP模式:" + a2.setScale(0, RoundingMode.HALF_UP));
System.out.println("HALF_UP模式:" + a3.setScale(0, RoundingMode.HALF_UP));
System.out.println("HALF_UP模式:" + a4.setScale(0, RoundingMode.HALF_UP)); - RoundingMode.UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException
1
2
3
4
51.5 => 抛出 ArithmeticException
1.1 => 抛出 ArithmeticException
1.0 => 1
-1.1 =>抛出 ArithmeticException
-1.6 => 抛出 ArithmeticException - RoundingMode.UP:远离零方向舍入 的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值
1
2
3
4
5
6
7
8
9
10
11
125.5 => 6
1.1 => 2
-1.0 => -1
-2.5 => -3
BigDecimal a1 = new BigDecimal("5.5");
BigDecimal a2 = new BigDecimal("1.1");
BigDecimal a3 = new BigDecimal("-1.0");
BigDecimal a4 = new BigDecimal("-2.5");
System.out.println("UP模式:" + a1.setScale(0, RoundingMode.UP));
System.out.println("UP模式:" + a2.setScale(0, RoundingMode.UP));
System.out.println("UP模式:" + a3.setScale(0, RoundingMode.UP));
System.out.println("UP模式:" + a4.setScale(0, RoundingMode.UP));
格式化
DecimalFormat 解析
符号 | 位置 | 描叙 |
---|---|---|
0 | 数字 | 阿拉伯数字,如果不存在则显示0 |
# | 数字 | 阿拉伯数字,如果不存在不显示0 |
. | 数字 | 小数分隔符或货币小数分隔符 |
, | 数字 | 分组分隔符 |
E | 数字 | 分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号 |
- | 数字 | 负号 |
; | 子模式边界 | 分隔正数和负数子模式 |
% | 子模式边界 | 乘以 100 并显示为百分数 |
\u2030 | 子模式边界 | 乘以 1000 并显示为千分数 |
¤(\u00A4) | 子模式边界 | 货币记号,由货币符号替换。如果两个同时出现,则用国际货币符号替换。如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符 |
’ | 子模式边界 | 用于在前缀或或后缀中为特殊字符加引号,例如 “‘#’#“将 123 格式化为 “#123”。要创建单引号本身,请连续使用两个单引号:”# o’'clock” |
1 | // 建立货币格式化引用 |
其他
科学计数法问题
1 | BigDecimal b = new BigDecimal("0.0000001"); |
当 BigDecimal的值 小于一定值时(测试时发现:小于等于0.0000001)时,则会被记为科学计数法。可以使用 toPlainString()方法显示原来的值
去除无效的 0
1 | BigDecimal b = new BigDecimal("0.000000100000000"); |
stripTrailingZeros() 方法的本质是去除掉多余的0,其返回数据类型是BigDecimal,同样的在使用时需要注意科学技术法的问题
保留小数位
1 | double num = 13.154215; |
大小比较
在比较两个BigDecimal的值是否相等时,要特别注意,使用 equals()
方法不但要求两个BigDecimal的 值相等 ,还要求它们的 scale()相等,因此如果只是比较数值的大小,必须使用 compareTo()
方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于
1 | BigDecimal d1 = new BigDecimal("123.456"); |
附录
- Java中浮点类型的精度问题 double float
- 廖雪峰 BigInteger
- 廖雪峰 BigDecimal
- BigDecimal
- Java 9中新的货币API