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

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

目录:

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

1. BigInteger

1.1 简介

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

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

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

1.2 常量

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 构造器

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就是负数的补码。因此通过补码的逆运算(补码的补码)可以得到负数的绝对值,再将符号位设置为-,则得到这个补码所代表的负数。

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。

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进制。

// 输出 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 静态工厂方法

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

1.5 运算

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

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

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:

BigInteger bigInteger3 = new BigInteger("12C", 16);
// 输出 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 常量

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 构造器

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

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

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。

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

将String表示形式转换成BigDecimal。

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)
// 输出 10000
System.out.println(BigDecimal.valueOf(10000L));

把long表示形式转换成BigDecimal。

  • (2) public static BigDecimal valueOf(double val)
// 输出 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)
// 输出 1.0000E-28
System.out.println(BigDecimal.valueOf(10000L, 32));

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

2.5 运算

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

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

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

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

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

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

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

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

public BigDecimal divide(BigDecimal divisor, int roundingMode)
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即可:

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

和BigInteger一样。

  • (6) 较大值
// 输出 0.123456789011111111111333373636633535353
System.out.println(bigDecimal0.max(bigDecimal1));
  • (7) 较小值
// 输出 0.123456789011111111111333373636611111111
System.out.println(bigDecimal0.min(bigDecimal1));
  • (8) 获取基本类型的值
// 输出 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) 转换成字符串
// 输出 0.123456789011111111111333373636633535353
System.out.println(bigDecimal0.toString());// 输出 0.123456789011111111111333373636633535353
System.out.println(bigDecimal0.toPlainString());

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

  • (10) 转换成BigInteger
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都有自己的精度)。

手续费处理类:


/*** 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'字符:


/*** 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);elsereturn 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;}
}

Java篇 - 最全BigInteger和BigDecimal实战相关推荐

  1. java之包装类与BigInteger、BigDecimal

    一.包装类 (1)包装类与原类型 Integer               int 的包装类 Boolean             boolean 的包装类 Character          ...

  2. java多叉树全路径_算法实战——多叉树全路径遍历

    本文为原创作品,首发于微信公众号:[坂本先生],如需转载请在文首明显位置标明"转载于微信公众号:[坂本先生]",否则追究其法律责任. 微信文章地址:实战算法--多叉树全路径遍历 前 ...

  3. Java番外篇4——BigInteger与BigDecimal

    Java番外篇4--BigInteger与BigDecimal 为了解决大数运算的问题 操作整型:BigInteger 操作小数:BigDecimal 1.BigInteger 方法声明 功能介绍 p ...

  4. Java基础-->一篇讲全Java常用类(详细易懂,建议收藏)

    Java基础–>一篇讲全Java常用类(详细易懂,建议收藏) 文章目录 Java基础-->一篇讲全Java常用类(详细易懂,建议收藏) 1.字符串相关的类 String类 概述 创建Str ...

  5. 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息

    第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...

  6. Java 大数类BigInteger与BigDecimal详细介绍(配蓝桥杯例题讲解)

    文章目录 1.基本函数 1.1 java.math.BigInteger.valueOf(long val) 2.运算法则 2.1 基本运算 2.2 compareTo(BigInteger othe ...

  7. Java常用类(5)--不可变的任意精度BigInteger、BigDecimal类

    文章目录 BigInteger类 BigDecimal类 BigInteger类 Integer类作为int的包装类,能存储的最大整型值为2^31-1,Long类也是有限的, 最大为2^63-1.如果 ...

  8. 第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单

    我们来了解一下 自定义菜单创建接口: http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_to ...

  9. 第三篇 :微信公众平台开发实战Java版之请求消息,响应消息以及事件消息类的封装...

    微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者 ...

最新文章

  1. C++用库 jsoncpp 解析 JSON
  2. HDU 2955 Robberies
  3. iOS中代码支持多国语言切换的实现(Xcode5+iOS7)
  4. 如何从几何角度上理解方程组只有一个解_深度科普---电磁波(三):无激励下的真空中的Maxwell方程组的解...
  5. Java反射机制是什么?
  6. java httpclient 返回xml_通过httpClient通过post向接口发送xml数据,并处理返回的xml报文...
  7. 深入理解C语言系列之C语言语法陷阱(考题常设置的陷阱点、必须避免的错误和缺陷类型)
  8. DBA一族九阳神功秘籍
  9. matlab 运行 AlexNet
  10. mysql配置和管理(转载)
  11. AI头发笔刷_这么棒的AI插件,一定要偷偷藏好了不让总监知道……
  12. Flash网页游戏辅助工具制作简析
  13. 【Linux】ROS机器人操作系统的安装与使用
  14. c语言中的正号运算符,C语言 运算符
  15. windows cmd命令行查看结束进程
  16. windows 下vscode coderunner+bash 编程
  17. 信息安全专业面试知识点整理(密码学与信数基础)
  18. WIN10系统右下角网络连接图标消失解决方案
  19. echarts 报错Failed to execute 'createRadialGradient' on 'CanvasRenderingContext2D': The provided doubl
  20. mock模拟接口测试 vue_VUE使用Mock模拟接口

热门文章

  1. 静态代码检查工具 cppcheck 的使用
  2. JavaSE自学笔记016_Real(多线程)
  3. VS2015报错C4996处理
  4. JVM G1 源码分析(七)- Full GC
  5. python期末大作业_上海交通大学python期末大作业题目(姚天昉)
  6. 中小学AI离线智能语音识别模块语音 图形化编程
  7. Flutter:Dialog对话框及自定义Dialog
  8. python namedtuple_python 简单了解namedtuple
  9. [概率DP]相逢是温厚
  10. A - The Fool HDU - 6555