您的位置:首页 > 编程语言 > Java开发

Java篇 - 最全BigInteger和BigDecimal实战

2019-01-18 17:56 369 查看

最近公司在做一款区块链钱包,区块链上传输的数值都是很大的,大到几十位。用Java的基本类型是处理不了的,int占32位,long、double占64位,如果用这些基本数据类型运算的话,第一是存储不了这么大的数,第二会出现精度丢失以及科学计数法等问题。

一般处理商业计算,如钱包,交易所等业务,都离不开这两个类。

 

目录:

  1. BigInteger
  2. BigDecimal
  3. 实战演练

 

 

1. BigInteger

 

1.1 简介

如果在操作的时候一个整型数据已经超过了整数的最大类型长度long的话,则此数据就无法装入,所以此时要使用BigInteger类进行操作。

[code]   // 2的63次方 - 1      9223372036854775807
System.out.println(Long.MAX_VALUE);

如果要存放一个数,如:1111111111111111111111111111111111111111111111111, long的范围是不够用的。

 

1.2 常量

[code]
public static final BigInteger ZERO = new BigInteger(new int[0], 0);

public static final BigInteger ONE = valueOf(1);

public static final BigInteger TEN = valueOf(10);

常量包括0,1,10。
 

1.3 构造器

[code]public BigInteger(byte[] val)

public BigInteger(int signum, byte[] magnitude)

public BigInteger(String val, int radix)

public BigInteger(String val)

public BigInteger(int numBits, Random rnd)

public BigInteger(int bitLength, int certainty, Random rnd)
  • (1) public BigInteger(byte[] val)

将一个包含二进制补码的字节数组转换成BigInteger,如果第一个字节是负数,则这个byte[] val就是负数的补码。因此通过补码的逆运算(补码的补码)可以得到负数的绝对值,再将符号位设置为-,则得到这个补码所代表的负数。

[code]BigInteger bigInteger0 = new BigInteger(new byte[]{1, 0, 0, 1, 0, 1, 1, 0});
// 输出 72057598332961024
System.out.println(bigInteger0.toString());

BigInteger bigInteger1 = new BigInteger(new byte[]{-1, 0, 0, 1, 0, 1, 1, 0});
// 输出 -72057589742894848
System.out.println(bigInteger1.toString());
  • (2) public BigInteger(int signum, byte[] magnitude)

这个构造方法很好理解,magnitude数组也是采用stripLeadingZeroBytes方法,将每个字节的二进制补码按顺序连接起来后去掉开头的0后返回,只是符号位可以通过传参赋予而已。

  • (3) public BigInteger(String val, int radix)

将指定进制的字符串表示形式转换为BigInteger。

[code]BigInteger bigInteger2 = new BigInteger("12C", 16);
// 输出 300
System.out.println(bigInteger2);

BigInteger bigInteger3 = new BigInteger("1500000000000000000000000", 10);
// 输出 1500000000000000000000000
System.out.println(bigInteger3);

BigInteger bigInteger4 = new BigInteger("1725", 8);
// 输出 981
System.out.println(bigInteger4);

注意16进制以0x开头,而参与运算的必须是纯数字,不能带0x,否则会报错。

  • (4) public BigInteger(String val)

和public BigInteger(String val, int radix)一样,radix为10进制。

[code]// 输出 1000000000000000
System.out.println(bigInteger5);
  • (5) public BigInteger(int numBits, Random rnd)

构造一个随机生成的 BigInteger,它是在0到(2^numBits- 1)范围内均匀分布的值。该分布的均匀性假定rnd中提供了一个随机位的公平源 (fair source)。注意,此构造方法始终构造一个非负BigInteger。

  • (6) BigInteger(int bitLength, int certainty, Random rnd)

构造一个随机生成的正 BigInteger,它可能是一个具有指定 bitLength的素数。相对于此构造方法,建议优先使用probablePrime 方法,必须指定一个确定数的情况除外。certainty表示调用方允许的不确定性的度量。新的BigInteger表示素数的概率超出(1 - 1/2^certainty)。此构造方法的执行时间与此参数的值是成比例的。

 

1.4 静态工厂方法

[code]public static BigInteger valueOf(long val)
[code]// 输出 123456789
System.out.println(BigInteger.valueOf(123456789L));

 

1.5 运算

[code]BigInteger bigInteger0 = new BigInteger("15000000000000000000000000000000000000");
BigInteger bigInteger1 = new BigInteger("15000000000000000000000000000000001111");
  • (1) 加
[code]// 输出 bigInteger0 + bigInteger1 = 30000000000000000000000000000000001111
System.out.println("bigInteger0 + bigInteger1 = " + bigInteger0.add(bigInteger1));
  • (2) 减
[code]// 输出 bigInteger0 - bigInteger1 = -1111
System.out.println("bigInteger0 - bigInteger1 = " + bigInteger0.subtract(bigInteger1));
  • (3) 乘
[code]// bigInteger0 * bigInteger1 = 225000000000000000000000000000000016665000000000000000000000000000000000000
System.out.println("bigInteger0 * bigInteger1 = " + bigInteger0.multiply(bigInteger1));
  • (4) 除
[code]// 输出 bigInteger0 / bigInteger1 = 0 只会留整数部分
System.out.println("bigInteger0 / bigInteger1 = " + bigInteger0.divide(bigInteger1));
  • (5) 比较大小
[code]// 输出 bigInteger0 compareTo bigInteger1 = -1 (0 : 相等,1 : 比它大,-1 : 比它小)
System.out.println("bigInteger0 compareTo bigInteger1 = " + bigInteger0.compareTo(bigInteger1));
  • (6) 较大值
[code]// 输出 bigInteger0 max bigInteger1 = 15000000000000000000000000000000001111
System.out.println("bigInteger0 max bigInteger1 = " + bigInteger0.max(bigInteger1));
  • (7) 较小值
[code]// bigInteger0 min bigInteger1 = 15000000000000000000000000000000000000
System.out.println("bigInteger0 min bigInteger1 = " + bigInteger0.min(bigInteger1));
  • (8) 获取基本类型的值

因为上面的bigInteger0和bigInteger1都已经超出了基本类型的范围,所以获取到的值肯定就是0或者不正确的。这边重写构造了一个较小值的BigInteger对象测试下:

[code]BigInteger bigInteger2 = new BigInteger("10000");

// 输出 10000
System.out.println("bigInteger2 int value = " + bigInteger2.intValue());

// 输出 10000,extra是精准的,如果范围超过31位,则会抛出异常
System.out.println("bigInteger2 int value extra = " + bigInteger2.intValueExact());

// 输出 10000
System.out.println("bigInteger2 long value = " + bigInteger2.longValue());

// 输出 10000,extra是精准的,如果范围超过64位,则会抛出异常
System.out.println("bigInteger2 long value = " + bigInteger2.longValueExact());
  • (9) 转换成字符串

再定义一个16进制作为参数的BigInteger:

[code]BigInteger bigInteger3 = new BigInteger("12C", 16);
[code]// 输出 bigInteger0 toString = 15000000000000000000000000000000000000
System.out.println("bigInteger0 toString radix 10 = " + bigInteger0.toString());

// 输出 bigInteger3 toString radix 10 = 300
System.out.println("bigInteger3 toString radix 10 = " + bigInteger3.toString());

// 输出 bigInteger0 toString radix 16 = 0xb48e51940c76a45816e51f000000000
System.out.println("bigInteger0 toString radix 16 = " + "0x" + bigInteger0.toString(16));

// 输出 bigInteger3 toString radix 16 = 0x12c
System.out.println("bigInteger3 toString radix 16 = " + "0x" + bigInteger3.toString(16));

 

 

2. BigDecimal

 

2.1 简介

float和double类型的主要设计目标是为了科学计算和工程计算。它们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。而且BigDecimal和BigInteger一样,能表示double不能承载的大小。

 

2.2 常量

[code]public static final BigDecimal ZERO = zeroThroughTen[0];

public static final BigDecimal ONE = zeroThroughTen[1];

public static final BigDecimal TEN = zeroThroughTen[10];

分别表示0,1,10。

 

2.3 构造器

[code]public BigDecimal(double val)

public BigDecimal(int val)

public BigDecimal(String val)
  • (1) public BigDecimal(double val)

将double表示形式转换为BigDecimal,这个是不建议使用的。看个例子:

[code]BigDecimal bigDecimal0 = new BigDecimal(2.3);
// 输出 2.29999999999999982236431605997495353221893310546875
System.out.println(bigDecimal0);

参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为double。

 

  • (2) public BigDecimal(int val)

将int表示形式转换成BigDecimal。

[code]BigDecimal bigDecimal1 = new BigDecimal(2);
// 输出 2
System.out.println(bigDecimal1);
  • (3) public BigDecimal(String val)

将String表示形式转换成BigDecimal。

[code]BigDecimal bigDecimal2 = new BigDecimal("2.3");
// 输出 2.3
System.out.println(bigDecimal2);

String 构造方法是完全可预知的:写入newBigDecimal("0.1")将创建一个BigDecimal,它正好等于预期的 0.1。因此比较而言,通常建议优先使用String构造方法。

 

2.4 静态工厂方法

 

  • (1) public static BigDecimal valueOf(long val)
[code]// 输出 10000
System.out.println(BigDecimal.valueOf(10000L));

把long表示形式转换成BigDecimal。

 

  • (2) public static BigDecimal valueOf(double val)
[code]// 输出 3.0
System.out.println(BigDecimal.valueOf(3.0));

这个和public BigDecimal(String val)功能一样,内部先会把double转成String再构造BigDecimal。

 

  • (3) public static BigDecimal valueOf(long unscaledVal, int scale)
[code]// 输出 1.0000E-28
System.out.println(BigDecimal.valueOf(10000L, 32));

将一个long非标度值和一个int标度转换成一个BigDecimal。

 

2.5 运算

[code]BigDecimal bigDecimal0 = new BigDecimal("0.123456789011111111111333373636633535353");
BigDecimal bigDecimal1 = new BigDecimal("0.123456789011111111111333373636611111111");
  • (1) 加
[code]// 输出 0.246913578022222222222666747273244646464
System.out.println(bigDecimal0.add(bigDecimal1));
  • (2) 减
[code]// 输出 2.2424242E-32
System.out.println(bigDecimal0.subtract(bigDecimal1));

可以看到打印出了科学技术法,因为System.out.println接收的是一个String,我在项目中也遇到过,怎么解决呢?

BigDecimal有一个toPlainString()方法,返回最原生的字符串:

[code]// 输出 0.000000000000000000000000000000022424242
System.out.println(bigDecimal0.subtract(bigDecimal1).toPlainString());

我在项目中,如果要把大数转换成字符串显示用的都是这个方法。

 

  • (3) 乘
[code]// 输出 0.015241578752934005200178336425557685621242732778329012054317625383597429607183
System.out.println(bigDecimal0.multiply(bigDecimal1).toPlainString());
  • (4) 除
[code]System.out.println(bigDecimal0.divide(bigDecimal1).toPlainString());

报错了:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

如果用BigDecimal做除法的时候一定要在divide方法中传递小数模式,否则在不整除的情况下,结果是无限循环小数时,就会抛出以上异常。

[code]public BigDecimal divide(BigDecimal divisor, int roundingMode)
[code]public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

scale为小数位数,roundingMode为小数模式:

ROUND_CEILING    如果BigDecimal是正的,则做 ROUND_UP操作;如果为负,则做ROUND_DOWN操作。
ROUND_DOWN     从不在舍弃(即截断)的小数之前增加数字。
ROUND_FLOOR    如果BigDecimal为正,则作ROUND_UP;如果为负,则作ROUND_DOWN。
ROUND_HALF_DOWN  若舍弃部分> .5,则作 ROUND_UP;否则,作ROUND_DOWN。
ROUND_HALF_EVEN   如果舍弃部分左边的数字为奇数,则作 ROUND_HALF_UP;如果它为偶数,则作ROUND_HALF_DOWN。
ROUND_HALF_UP  若舍弃部分>=.5,则作 ROUND_UP;否则,作ROUND_DOWN 。
ROUND_UNNECESSARY  该"伪舍入模式"实际是指明所要求的操作必须是精确的,因此不需要舍入操作。
ROUND_UP   总是在非0舍弃小数(即截断)之前增加数字。

所以上面我只要改一下,加上roundingMode即可:

[code]// 输出 1.000000000000000000000000000000181636362
System.out.println(bigDecimal0.divide(bigDecimal1, BigDecimal.ROUND_HALF_UP).toPlainString());
  • (5) 比较大小
[code]// 输出 1
System.out.println(bigDecimal0.compareTo(bigDecimal1));

和BigInteger一样。

 

  • (6) 较大值
[code]// 输出 0.123456789011111111111333373636633535353
System.out.println(bigDecimal0.max(bigDecimal1));
  • (7) 较小值
[code]// 输出 0.123456789011111111111333373636611111111
System.out.println(bigDecimal0.min(bigDecimal1));
  • (8) 获取基本类型的值
[code]// 输出 0
System.out.println(bigDecimal0.intValue());

// 精度超出范围,抛出异常
System.out.println(bigDecimal0.intValueExact());

// 输出 0
System.out.println(bigDecimal0.longValue());

// 精度超出范围,抛出异常
System.out.println(bigDecimal0.longValueExact());

// 输出 0.12345678901111111 只能显示double能表示的精度
System.out.println(bigDecimal0.doubleValue());

看项目需求,我一般不会直接调用doubleValue(),而是调用toPlainString(),再按需求截取字符串显示,这样更能满足要求。

 

  • (9) 转换成字符串
[code]// 输出 0.123456789011111111111333373636633535353
System.out.println(bigDecimal0.toString());

// 输出 0.123456789011111111111333373636633535353
System.out.println(bigDecimal0.toPlainString());

尽量使用toPlainString(),这样不会出现科学技术法和其他显示问题。

 

  • (10) 转换成BigInteger
[code]BigDecimal bigDecimal = new BigDecimal("1234.2727277272722272727277222");

BigInteger bigInteger = bigDecimal.toBigInteger();
// 输出 1234,即整数部分
System.out.println(bigInteger);

BigInteger bigInteger1 = bigDecimal.toBigIntegerExact();
// 因为bigDecimal包含小数部分,所以这边toBigIntegerExact会报错 java.lang.ArithmeticException: Rounding necessary
System.out.println(bigInteger1);

BigDecimal bigDecimal2 = new BigDecimal("1234");
BigInteger bigInteger2 = bigDecimal.toBigIntegerExact();
// 这样没有问题,输出 1234
System.out.println(bigInteger2);

 

 

3. 实战演练

下面是我们项目大数和手续费处理方法,因为从以太坊上拿到的数值都是大数,在交易的时候用的是大数来传输,而客户端显示的时候,需要做精度转换处理(比如eth的精度是18,其他的token都有自己的精度)。

 

手续费处理类:

[code]
/**
* Gas converter.
*
* @author kuang on 2018/10/29.
*/
public final class GasConverter {

private static final String[] GAS_UNITS = {
"turing", "yau", "maxwell", "cajal", "hinton", "minsky", "ctxc"
};

private static final int FACTOR = 1000;

public static Gas convert(BigInteger gasValue) {
if (Requires.isNull(gasValue))
return null;
BigDecimal value = BigDecimal.ZERO;
BigDecimal gasValueDecimal = new BigDecimal(gasValue);
String unit = "";
BigDecimal divider = new BigDecimal(String.valueOf(FACTOR));
for (int index = 0, len = GAS_UNITS.length; index < len; ++index) {
if (gasValueDecimal.compareTo(divider) < 0 || index == len - 1) {
value = gasValueDecimal;
unit = GAS_UNITS[index];
break;
}
gasValueDecimal = gasValueDecimal.divide(divider, BigDecimal.ROUND_UP);
}
return new Gas(value, unit);
}

public static BigDecimal convert(BigInteger gasValue, String unit) {
if (Requires.isNull(gasValue, unit))
return BigDecimal.ZERO;
int index = getIndex(unit);
if (index == 0)
return new BigDecimal(gasValue);
else if (index > 0)
return new BigDecimal(gasValue).divide(BigDecimal.TEN.pow(index * 3), BigDecimal.ROUND_UP);
throw new UnsupportedOperationException("unsupport unit");
}

public static BigInteger restore(BigDecimal value, String unit) {
if (Requires.isNull(unit))
return BigInteger.ZERO;
int index = getIndex(unit);
if (index == 0)
return value.toBigInteger();
else if (index > 0)
return value.multiply(BigDecimal.TEN.pow(index * 3)).toBigInteger();
throw new UnsupportedOperationException("unsupport unit");
}

public static BigDecimal convertToCTXC(BigInteger gasValue) {
if (Requires.isNull(gasValue))
return BigDecimal.ZERO;
return new BigDecimal(gasValue).divide(BigDecimal.TEN.pow((GAS_UNITS.length - 1) * 3), BigDecimal.ROUND_UP);
}

private static int getIndex(String unit) {
int index = -1;
for (int i = 0, len = GAS_UNITS.length; i < len; i++) {
if (GAS_UNITS[i].equals(unit)) {
index = i;
break;
}
}
return index;
}
}

上面包含手续费的各种转换操作。

 

将大数转换成客户端可读的数值,目前是保留小数点9位,我这边用的是字符串截取的方法,同时要去除后面无效的'0'字符:

[code]
/**
* Token BigDecimal/BigInteger converter.
*
* @author kuang on 2018/11/12.
*/
public final class TokenBigConverter {

private static final String HEX_PREFIX = "0x";

public TokenBigConverter() {
throw new RuntimeException("TokenBigConverter Stub!");
}

private static boolean isValidHexQuantity(String value) {
if (value == null) {
return false;
}
if (value.length() < 3) {
return false;
}
if (!value.startsWith(HEX_PREFIX)) {
return false;
}
return true;
}

private static BigInteger decodeQuantity(String value) {
if (!isValidHexQuantity(value)) {
throw new MessageDecodingException("Value must be in format 0x[1-9]+[0-9]* or 0x0");
}
try {
return new BigInteger(value.substring(2), 16);
} catch (NumberFormatException e) {
throw new MessageDecodingException("Negative ", e);
}
}

public static BigInteger toBigInteger(String value) {
if (value.startsWith(HEX_PREFIX))
return decodeQuantity(value);
else
return new BigInteger(value);
}

public static BigDecimal toBigDecimal(BigDecimal value, int decimals) {
return value.divide(BigDecimal.TEN.pow(decimals), BigDecimal.ROUND_UP);
}

public static BigDecimal toBigDecimal(BigInteger value, int decimals) {
return toBigDecimal(new BigDecimal(value), decimals);
}

public static BigInteger toBigInteger(BigInteger value, int decimals) {
return value.multiply(BigInteger.TEN.pow(decimals));
}

public static BigInteger toBigInteger(String value, int decimals) {
return new BigDecimal(value).multiply(BigDecimal.TEN.pow(decimals)).toBigInteger();
}

public static String toDecimalString(BigDecimal value, int decimal) {
return toDecimalString(value.toPlainString(), decimal);
}

public static String toDecimalString(String value, int decimal) {
if (value.contains(".")) {
int offset = value.indexOf(".");
int decimalLen = value.length() - 1 - offset;
if (decimalLen > decimal)
value = value.substring(0, offset + (decimal + 1));
value = deleteTailZeroChars(value);
}
return value;
}

public static String deleteTailZeroChars(String value) {
if (TextUtils.isEmpty(value))
return "";
String[] splits = StringUtils.fastSplit(value, '.');
if (splits != null && splits.length == 2) {
int notZeroIndex = -1;
int end = splits[1].length() - 1;
for (int index = end; index >= 0; index--) {
if (splits[1].charAt(index) != '0') {
notZeroIndex = index;
break;
}
}
if (notZeroIndex >= 0) {
if (notZeroIndex == end)
return value;
else {
return new StringBuilder(splits[0]).append(".")
.append(splits[1].substring(0, notZeroIndex + 1)).toString();
}
} else {
return splits[0];
}
}
return value;
}
}

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: