[Java SE] 經典問題:超出Java Long型(8字節/64位)的二進制比特流數據如何進行大數的數值計算?

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 參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章