• [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 参考文献

  • 相关阅读:
    grpc protoBuf 编码/解码原理
    JVM对高并发的支持-volatile
    Linux CentOS系统安装mysql8.0.31(tar方式)
    什么是仲裁器(Arbiter)?
    【并发编程】ThreadLocal详解
    小学生python游戏编程arcade----基本知识1
    Spring Cloud:四 【详细】
    记一次Postgresql从堆叠注入到RCE
    斗志斗勇之JVM
    小李学知识之redis相关(redis面试题)
  • 原文地址:https://www.cnblogs.com/johnnyzen/p/18133094