0 問題描述
- 經典問題:超出Java Long型(8位元組/64位)的二進位制位元流資料如何進行大數的數值計算?
近期工作上遇到了這個問題:需要將一個無符號數、且位長
>=
8位元組(等於8位元組時,首位bit為1,其他bit不全為0)的二進位制字串轉為Java物件(此處我先稱之為:原始整數),進行整型運算、或浮點數運算浮點運算的思路:result = 原始整數 * 精度 + 偏移量
- 原來的思路:(存在問題)
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= -9223372036854775808 , +2^ 63 -1 = 9223372036854775807 ]
[1000000000000000000000000000000000000000000000000000000000000000, 0111111111111111111111111111111111111111111111111111111111111111]
即:
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
01111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
- 解決思路:將二進位制字串轉為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;
}
X 參考文獻
- [車聯網] CAN匯流排與DBC詳解 - 部落格園/千千寰宇