[Java SE] Java经典问题:超出Java Long型(8字节/64位)的二进制比特流数据如何进行大数的数值计算?
目录
- 0 问题描述
- 1 关键问题的实现过程示例
- 1.1 测试用例1:无符号数、且位长>=8字节(等于8字节时,首位bit为1,其他bit不全为0)的情况
- 1.2 测试用例2:有符号数、且位长>=8字节(等于8字节时,首位bit为1,其他bit不全为0)的情况
- 1.X 工具方法
- 延伸:Java Double 取值范围
- 延伸:Java BigInteger
- X 参考文献
0 问题描述
- 经典问题:超出Java Long型(8字节/64位)的二进制比特流数据如何进行大数的数值计算?
近期工作上遇到了这个问题:需要将一个无符号数、且位长
>=
8字节(等于8字节时,首位bit为1,其他bit不全为0)的二进制字符串转为Java对象(此处我先称之为:原始整数),进行整型运算、或浮点数运算浮点运算的思路:result = 原始整数 * 精度 + 偏移量
- 在 Java 中,
long
是一种 有符号整数类型。它占用64
位(8 字节),最高位用于表示符号:
- 如果最高位是
0
,表示该 long 值是正数。- 如果最高位是
1
,表示该 long 值是负数。
- 原来的思路:(存在问题)
Long originIntegerValue = Long.parseLong(binaryString, 2); //将二进制字符串转为Long整型对象(有符号数、容量:8byte/64bit)
BigDecimal resolution = new BigDecimal( getResolution().toString() );
BigDecimal calResult = (new BigDecimal(originIntegerValue)).multiply(resolution.stripTrailingZeros()).add(new BigDecimal( getOffset().toString() ));//最终基于浮点数运算
在上面这种极端情况下,第1行代码会报错:(超出了Java Long对象的取值范围)
Exception in thread "main" java.lang.NumberFormatException: For input string: "1100000001000000110010110000000000000000000000000000000000000000"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:592)
- 复习一下 : Java Long 型(8Byte / 64Bit)的取值范围
[-2^ 63= -922 3372 0368 5477 5808 , +2^ 63 -1 = 922 3372 0368 5477 5807 ]
[1000000000000000000000000000000000000000000000000000000000000000, 0111111111111111111111111111111111111111111111111111111111111111]
即:
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
01111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
补充:
Java
的Float
的取值范围:
Float.MAX_VALUE
:3.4028235E38
Float.MIN_VALUE
:1.4E-45
- 解决思路:将二进制字符串转为byte数组,再转为BigInteger大整型数。即可基于BigInteger对象进行整数运算。如果基于进行浮点运算时,可将 BigInteger 大整型数对象再转为 BigDecimal。
BigInteger originIntegerValue= new BigInteger(1, bytes)
// 使用字节数组创建BigInteger【扩展/补充】BigInteger还支持 Long /
new BigDecimal bigDecimal = new BigDecimal(originIntegerValue)
//最终,基于bigDecimal进行浮点数运算
1 关键问题的实现过程示例
-
现在的关键点,转为了:如何将二进制字符串转为BigInteger (转换过程中不能借助Long)
-
二进制数据:"1100000001000000110010110000000000000000000000000000000000000000" (需考虑————情况1:作为有符号数;情况2:作为无符号数)
16进制:
0xc040cb0000000000L
11000000
01000000
11001011
00000000
00000000
00000000
00000000
00000000
1.1 测试用例1:无符号数、且位长>=
8字节(等于8字节时,首位bit为1,其他bit不全为0)的情况
/** 针对 长度为 64 bit、无符号数 的CAN信号,且第1位为1的情况 :使用 BigInteger
* @description Java中没有内置的无符号8字节整数类型,但是可以使用 `java.math.BigInteger` 类来处理任意大的整数值,包括无符号整数
* @refernce-doc
**/
public static void unsigned8BytesDataTest(){
// 一个8字节的无符号整数
long longValue = 0xc040cb0000000000L; //0x10000000000000000L;
String longStr = "c040cb0000000000";//canFrameContent
// 转为二进制字符串
String binStr = BytesUtil.hexStringToBinaryString(longStr);
System.out.println("binStr: " + binStr);//1100000001000000110010110000000000000000000000000000000000000000
// 将无符号长整数转换为 BigInteger | 方式1: BigInteger
BigInteger value = toUnsignedBigInteger(longValue);
System.out.println("value : " + value);//1385 3295 6546 5208 4224
//二进制字符串转Java数据对象 | 测验 Long.parseLong(binStr , 2) | 若没有报错,则说明OK
BigInteger value2 = toUnsignedBigInteger(binStr);
System.out.println("value2 : " + value2);//1385 3295 6546 5208 4224
//二进制字符串转Java数据对象 | 测验 Long.parseLong(binStr , 2) | 若没有报错,则说明OK
Long value3 = Long.parseLong(binStr, 2);
System.out.println("value3 : " + value3);//报错信息如下
// Exception in thread "main" java.lang.NumberFormatException: For input string: "1100000001000000110010110000000000000000000000000000000000000000"
// at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
// at java.lang.Long.parseLong(Long.java:592)
// at ParseTest.unsigned8BytesDataTest(ParseTest.java:213)
// at ParseTest.main(ParseTest.java:29)
}
1.2 测试用例2:有符号数、且位长>=
8字节(等于8字节时,首位bit为1,其他bit不全为0)的情况
/**
* 有符号数、8字节
* 最终目标: 二进制字符串 转 Java 数据对象
*/
public static void signed8BytesDataTest(){
// 一个8字节的无符号整数
long longValue = 0xc040cb0000000000L; //0x10000000000000000L;
String longStr = "c040cb0000000000";//canFrameContent
// 转为二进制字符串
String binStr = BytesUtil.hexStringToBinaryString(longStr);
System.out.println("binStr: " + binStr);//1100000001000000110010110000000000000000000000000000000000000000
// 将有符号长整数转换为 BigInteger | 方式1: BigInteger
BigInteger value = toUnsignedBigInteger(longValue);
System.out.println("value : " + value);//-459 3448 4190 5746 7392
//二进制字符串转Java数据对象 | 测验 Long.parseLong(binStr , 2) | 若没有报错,则说明OK
BigInteger value2 = toUnsignedBigInteger(binStr);
System.out.println("value2 : " + value2);//1385 3295 6546 5208 4224
//二进制字符串转Java数据对象 | 测验 Long.parseLong(binStr , 2) | 若没有报错,则说明OK
Long value3 = Long.parseLong(binStr, 2);
System.out.println("value3 : " + value3);//报错信息如下
// Exception in thread "main" java.lang.NumberFormatException: For input string: "1100000001000000110010110000000000000000000000000000000000000000"
// at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
// at java.lang.Long.parseLong(Long.java:592)
// at ParseTest.signed8BytesDataTest(ParseTest.java:241)
// at ParseTest.main(ParseTest.java:30)
}
1.X 工具方法
toUnsignedBigInteger(long unsignedLong/String binStr)
private static BigInteger toUnsignedBigInteger(long unsignedLong) {
// 将无符号的8字节长整数转换为字节数组
byte[] bytes = ByteBuffer.allocate(8).putLong(unsignedLong).array();
// 使用字节数组创建BigInteger
return new BigInteger(1, bytes);
}
/** 二进制字符串 **/
private static BigInteger toUnsignedBigInteger(String binStr) {
byte[] bytes = null;
try {
// 将无符号的8字节长整数转换为字节数组
bytes = BytesUtil.binaryStringToBinaryArray(binStr);
} catch (Exception exception) {
log.error("Fail to convert as big integer!binStr : {}, exception : {}", binStr, exception);
}
// 使用字节数组创建BigInteger
return new BigInteger(1, bytes);
}
binaryStringToBinaryArray(binStr)
/**
* 二进制字符串转二进制数组
* @param binaryString
* @return
*/
public static byte[] binaryStringToBinaryArray(String binaryString) {
if(ObjectUtils.isEmpty(binaryString)){
throw new RuntimeException("Fail to convert binary array cause by the empty binary string! binaryString : " + binaryString);
}
if(binaryString.length() %8 != 0){//不是8的倍数
throw new RuntimeException("Fail to convert binary array cause that the binary string is not a multiple of 8! binaryString : " + binaryString);
}
// char [] charArray = binaryString.toCharArray() // string 内部由 2个字节的char组成的 char 数组 , 故: 这种转换做法有风险
// byte [] binaryArray = new byte [ binaryString.length() ];
// for (int i = 0; i < charArray.length; i ++) {
// //binaryArray[i] = (byte)charArray[i]; // java char 占用 : 2个字节 ; java byte 占用 1个字节 => 这种做法不正确
// binaryArray[i]
// }
int byteSize = binaryString.length()/8;
byte[] binaryArray = new byte[byteSize];
for (int i = 0; i < byteSize; i ++) {
String byteBinaryStr = binaryString.substring(i*8, i*8 + 8);//sample "01001000"
binaryArray[i] = binaryStringToByte(byteBinaryStr);
}
return binaryArray;
}
延伸:Java Double 取值范围
- 在Java中,
double
类型是一种双精度、64
位(8
个字节)的IEEE 754
浮点数。
double
类型在Java
中是基于IEEE 754
标准实现的双精度64位浮点数。- 在
IEEE 754
标准中,一个64位的双精度浮点数由以下几个部分组成:
- 符号位(Sign bit):
1
位,用于表示正负号。- 指数位(Exponent):
11
位,用于存储以2为底的指数部分。- 尾数位(Mantissa)或有效数字位(Significand):
52
位,用于存储实际的数值部分。
- 这种结构允许
double
类型表示的数值范围非常广泛,从大约$2.225 \times 10^{-308}$
到大约$1.797 \times 10^{308}$
,并且能够提供约15
位十进制数的精度。
- 这意味着它能够表示非常大的数值范围,同时也包括非常小的数值和非常大但接近零的数值。
- 范围
- double类型的数值范围可以从非常接近于零的负数到非常接近于零的正数,具体如下:
- 正的最大值:
Double.MAX_VALUE
,大约为1.7976931348623157E308
。- 负的最大值:
-Double.MAX_VALUE
,大约为-1.7976931348623157E308
。- 最小的正非零值:
Double.MIN_VALUE
,大约为4.9E-324
。- 负的最小正非零值:
-Double.MIN_VALUE
,大约为-4.9E-324。
- 精度
由于是浮点数,
double
类型在表示非常大或非常小的数时可能会有精度损失。
例如,尝试表示某些非常大的数或非常小的数时,可能会超出其精确度范围而变得不准确。
- 表示法
double
类型的字面量可以直接在代码中写出来,例如:
double myNumber = 123.456;
double largeNumber = 1.7976931348623157E308; // 接近于Double.MAX_VALUE
double smallNumber = 4.9E-324; // 接近于Double.MIN_VALUE
- 特殊值
double
类型还有一些特殊的值:
- 正无穷大:
Double.POSITIVE_INFINITY
- 负无穷大:
Double.NEGATIVE_INFINITY
NaN
(不是一个数字):Double.NaN
,用于表示未定义或不可表示的值。例如,0.0除以0.0的结果是NaN。
- 检查特殊值
你可以使用静态方法检查一个double值是否为无穷大或NaN:
double value = ...;
if (Double.isInfinite(value)) {
System.out.println("无穷大");
} else if (Double.isNaN(value)) {
System.out.println("不是一个数字");
} else {
System.out.println("普通数值");
}
了解这些特性对于处理double类型的数值至关重要,尤其是在进行数学计算和数值分析时。
- 经典案例:
//1844 6744 0737 0955 1615
Double x = Double.valueOf("18446744073709551615");//1.8446744073709552E19
延伸:Java BigInteger
-
在 Java 中,BigInteger 类用于表示任意精度的整数。
-
BigInteger 类有一个构造函数
BigInteger(int signum, byte[] magnitude)
,其中 signum 和 magnitude 是两个参数。
signum
:这是一个整数,表示 BigInteger 的符号。它可以是以下两个值之一:
1
:表示正数。- -1`:表示负数。
magnitude
:这是一个字节数组,表示 BigInteger 的绝对值(即不考虑符号的数值部分)。数组中的字节按照大端字节序(最高有效字节在前)排列。因此,当你调用 BigInteger(1, bytes) 时,1 表示你正在创建一个正数 BigInteger。bytes 数组则包含了该正数的二进制表示(绝对值)。
- 例如:
byte[] bytes = {0, 0, 0, 1}; // 4字节表示,值为1(0x00000001)
BigInteger bigInt = new BigInteger(1, bytes); // 创建一个值为1的正BigInteger
System.out.println(bigInt); // 输出:1
在这个例子中,bytes 数组表示一个 32 位整数 1(因为数组有 4 个字节,每个字节都是 0,除了最低有效字节是 1)。signum 为 1 表示这是一个正数。因此,bigInt 的值是 1。
如果 signum 为 -1,则表示创建的 BigInteger 是一个负数:
byte[] bytes = {0, 0, 0, 1}; // 4字节表示,值为1(0x00000001)
BigInteger bigInt = new BigInteger(-1, bytes); // 创建一个值为-1的负BigInteger
System.out.println(bigInt); // 输出:-1
在这个例子中,bigInt 的值是 -1,因为 signum 为 -1 表示这是一个负数。
- 例如:
0x61
= 97L = '0110 00001' ='a'
的转换示例
//有符号数
Long.parseLong("61", 16); //97L
//有符号数 / 1:正数
new BigInteger(1, new byte [] { 0x61 }).toString() //"97"
new BigInteger(1, new byte [] { 61 }).toString() //"61"
//有符号数 / -1:负数
new BigInteger(-1, new byte [] { 0x61 }).toString() //"-97"
new BigInteger(-1, new byte [] { 61 }).toString() //"-61"
X 参考文献

本文链接: https://www.cnblogs.com/johnnyzen/p/18133094
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步