Java的BigInteger的使用存储实现 BigDecimal的精度问题准确保留位数和四舍五入
最近在刷算法题时,遇到用 int 甚至是 long都不能表示的超大整数型数据。就只能用刚学Java时学过的java.math.BigInteger来表示。对于精度丢失问题,应该用BigDecimal来解决。
BigInteger
ps:
1.byte:byte(1字节长度)类型的整数在内存里占8位,范围是:-128(-2的7次方) ~ 127 (2的7次方-1)
字节:计算机的处理信息的最小单位。1字节=8位(bit)二进制数。
2.short:short(2个字节长度)类型整数在内存里占16位,表示范围-32,768 ~ 32,767
3.int:int(4个字节长度)类型在内存中32位,表示范围-2,147,483,648 ~ 2,147,483,647(十亿级)
4.long:long(8个字节长度)类型在内存中64位,表示范围-9,223,372,036,854,775,808
~9,223,372,036,854,775,807
正如大家所记得一样,计算机对数据的存放和运算 以 补码 形式,对数据的显示 以 原码形式;
对于 BigInteger也是一样,以二进制补码形式存放与操作。
ps:补码
最高位其实是符号位,1表示负数,0表示正数;
正数的原码/反码/补码 都一样, 负数的 补码 = 原码求反码 再 + 1;
所谓反码:对原码按位取反,只是最高符号位保持不变。
这里的原 反 补 都是以二进制形式的。
基本操作:
1.常用转型方式:
方法 | 说明 |
BigInteger(String val) | 将数据字符串转化为BigInteger型数据 |
valueOf(long val) | 返回其值等于指定 long 的值的 BigInteger。 |
toString() | 返回此 BigInteger 的十进制字符串表示形式。 |
doubleValue() | 将此 BigInteger 转换为 double |
longValue() | 将此 BigInteger 转换为 long。 |
2.运算方法
BigInteger 提供所有了Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。 详情请看api手册
方法 | 说明 |
add(BigInteger val) | 加法 |
multiply(BigInteger val) | 乘法 |
divide(BigInteger val) | 除法,“/“ |
divideAndRemainder(BigInteger val) | 返回商和余数的数组 |
equals(Object x) | 比较是否相等 |
abs() | 绝对值 |
gcd(BigInteger val) | 最大公约数 |
…… | …… |
存储
正如前面所说,BigInteger以二进制补码形式存放.
但是 如果数据超出long的范围,那么在内存中以二进制补码的形式存放的时候,其位数会多余64,也就是会多余8个字节。
那么它到底怎么,存储的呢?
由源码可见,它使用一个int型整数来存储,BigInteger的符号。
当signum为-1时,表示该数为负;当signum为0时,表示该数为0;当signum为1时,表示该数为正数。
那么,具体的值是怎么表示的呢。
由源码可得它是,通过数组来存放非符号外的其他数据信息。特别的,当BigInteger为0时,该数组的长度为0.
比如:数:12345678901234567890(已经超出long的范围)
通过debug模式可得:
由此可见,数组中并不是存放的数据的每一位的数值。
通过多组数据debug观察:
当存入的数在int类型的范围内时,数组mag[0],就会直接存放这个数。
而当超过,int而其二进制总位数不超过32时,用mag[0]一个就能表示,mag[0]的补码与真实数据
是相同的。位数也超过32就只能加上数组的mag[1]一起表示。mag[0]与mag[1]的关系为,mag[1]
后32位二进制数表示的数。mag[0]时剩余的位数表示的数,两者是相并的关系。
更大的数以此类推。
BigDecimal
在Java中进行浮点数运算的时候,会出现丢失精度的问题。这个在不需要非常准确计算是可以满足要求的。但是在某些情况下,是万万不可得。比如银行得系统中,如果出现小小得误差那么慢慢得会出现惊人得量变。而且当小数太大或需要得位太多时float和double也不能表示。
ps: Java中float的精度为6-7位有效数字。double的精度为15-16位。
对于BigDecimal得基本操作详情请看api手册。
ps:
BigDecimal除法可能出现不能整除的情况,比如 9.9/1.3,这时会报错java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
//比较两种声明方式的精度问题
BigDecimal a = new BigDecimal(0.001);
BigDecimal b = new BigDecimal(0.001);
BigDecimal c = new BigDecimal("0.001");
BigDecimal d = new BigDecimal("0.002");
System.out.println(a.add(a));
System.out.println(c.add(d));
输出:
0.002000000000000000041633363423443370265886187553405761718750
0.003
由此可得当采用BigDecimal(double val)构造方法时,声明的BigDecimal类型的数据任可能出
现精度丢失问题。
故,在使用的时候,应该尽量不用该方法。可以采用将double转为String再使用BigDecimal(String val)的方法。
四舍五入:
setScale(int newScale, RoundingMode roundingMode)
newScale:要返回的 BigDecimal 值的标度。
roundingMode:要应用的舍入模式。
返回 BigDecimal,其标度为指定值,其非标度值通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定,以维护其总值。
//先将double类型转为String型,避免精度丢失,再转为BigDecimal型
BigDecimal pi = new BigDecimal(String.valueOf(Math.PI));
//RoundingMode:枚举类,各种舍入模式
System.out.println(pi.setScale(7, RoundingMode.HALF_UP ));//四舍五入
舍入模式集合:
模式 | 解释 |
CEILING | 向正无限大方向舍入的舍入模式。 |
DOWN | 向零方向舍入的舍入模式。 |
FLOOR | 向负无限大方向舍入的舍入模式。 |
HALF_DOWN | 五舍六入 |
HALF_EVEN | 四舍六入五作偶 |
HALF_UP | 四舍五入 |
UNNECESSARY | 用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。 |
UP | 远离零方向舍入的舍入模式。 |
感谢观看!