简体   繁体   中英

Interpret a negative number as unsigned with BigInteger

Is it possible to parse a negative number into an unsigned value with Java's BigInteger ?

So for instance, I'd to interpret -1 as FFFFFFFFFFFFFFFF .

Try using the constructor

public BigInteger(int signum, byte[] magnitude)

The first parameter should be set to 1 to specify you want to create a positive number. The byte array is the number you are parsing in BIG ENDIAN ORDER. It should be interpreted as an unsigned number if you set the first parameter to 1. The only trick is getting your number into a byte array, but that shouldn't bee too difficult.

EDIT: Looks like you have to do some manual bit arithmetic here. If I understand your problem correctly, you need to interpret a String as a Long and then interpret that long as unsigned and store that in the BigInteger class. I would do this.

public BigInteger getValue(String numberString)
{
   Long longValue = Long.valueOf(numberString);
   byte [] numberAsArray = new byte[8];  
   for(int i = 0; i < 8; i++)
   {  
      numberAsArray[7 - i] = (byte)((longValue >>> (i * 8)) & 0xFF);
   }
   return new BigInteger(1, numberAsArray);
}  

If you are thinking of a two's complement, you must specify a working bit length. A Java long has 64 bits, but a BigInteger is not bounded.

You could do something as this:

// Two's complement reference: 2^n . 
// In this case, 2^64 (so as to emulate a unsigned long)
private static final BigInteger TWO_COMPL_REF = BigInteger.ONE.shiftLeft(64);

public static BigInteger parseBigIntegerPositive(String num) {
    BigInteger b = new BigInteger(num);
    if (b.compareTo(BigInteger.ZERO) < 0)
        b = b.add(TWO_COMPL_REF);
    return b;
}

public static void main(String[] args) {
    System.out.println(parseBigIntegerPositive("-1").toString(16));
}

But this would implicitly mean that you are working with BigIntegers in the 0 - 2^64-1 range.

Or, more general:

public static BigInteger parseBigIntegerPositive(String num,int bitlen) {
    BigInteger b = new BigInteger(num);
    if (b.compareTo(BigInteger.ZERO) < 0)
        b = b.add(BigInteger.ONE.shiftLeft(bitlen));
    return b;
}

To make it more fooproof, you could add some checks, eg

public static BigInteger parseBigIntegerPositive(String num, int bitlen) {
    if (bitlen < 1)
        throw new RuntimeException("Bad bit length:" + bitlen);
    BigInteger bref = BigInteger.ONE.shiftLeft(bitlen);
    BigInteger b = new BigInteger(num);
    if (b.compareTo(BigInteger.ZERO) < 0)
        b = b.add(bref);
    if (b.compareTo(bref) >= 0 || b.compareTo(BigInteger.ZERO) < 0 )
        throw new RuntimeException("Out of range: " + num);
    return b;
}

There are two answers to the question, depending on whether the OP wants the answer to pertain to:

  1. Reading/Parsing a String and storing the interpreted value as a positive BigInteger .
  2. Outputting a negative BigInteger to a String as a positive hex number.

The Java BigInteger

First of it is important to understand that BigInteger actually stores numbers as two separate values:

  • a flag that indicates that the number is:
    • signed
    • zero
    • positive
  • an array of integers that contains the positive value of the number in big-endian order.

This means that -1 is stored as {signed, 1} .

Secondly, it is also important to understand that displaying a BigInteger as hex with a specific number of digits based on a specific number of bits used for storage has nothing to do with the number value, but everything to do with the number representation.

That is to say that for twos complement 32-bit values (ie int ) the following representations all represent the same stored value in memory:

  • -1
  • 4_294_967_295
  • ffffffff

While the same can not be said for BigInteger :

  • -1 (stored as {negative, 1} )
  • 4_294_967_295 (stored as {positive, 4_294_967_295} )
  • ffffffff (stored as {positive, 4_294_967_295} )

Reading/Parsing a String and storing the interpreted value as a positive BigInteger

To read a String that might contain a negative number and store it in a BigInteger as a positive number with a specific number of bits use the following code:

BigInteger number = new BigInteger("-1");
int bits = 64;

BigInteger maxValue = BigInteger.valueOf(number).shiftLeft(bits);

number = number.mod(maxValue);

Eg The value -1 in BigInteger using 16 bits would be stored as:

  • {positive, 65535} (ffff in hex)

Converting a [negative] BigInteger to a positive hex String

To output a BigInteger as a hexadecimal number adjusted to a specific number of bits use the following code:

BigInteger number = new BigInteger("-1");
int bits = 64;

BigInteger maxValue = BigInteger.valueOf(number).shiftLeft(bits);
number = number.mod(maxValue);

String outString String.format("%0" + (bits + 3)/4 + "X", number);

Performance

If you are concerned over the fact that the above code uses division to get the result then the following code will do the same for numbers that can be represented within the range (ie that can be stored using the specified number of bits):

BigInteger number = new BigInteger("-1");
int bits = 64;

if (number.signum() < 0) {
    number = BigInteger.valueOf(1).shiftLeft(bits).add(number);
}

String outStr = String.format("%0" + (bits + 3)/4 + "X", number);

Note that the code shown above does not deal with numbers that fall outside of the intended bit range.

Eg 1_000_000 and -1_000_000 as a 16-bit numbers will be converted to the strings:

  • F4240
  • -E4240

Where the expected values should be (which the modulo version actually returns):

  • 4240
  • BDC0

The full solution

The following code combines all of the above into one method:

public String AsAFixedBitsNumber(BigInteger number, int bits) {
    BigInteger maxValue = BigInteger.valueOf(1).shiftLeft(bits);

    if (maxValue.negate().compareTo(number) >= 0 || maxValue.compareTo(number) <= 0) {
        number = number.mod(maxValue);
    } else if (number.signum() < 0) {
        number = maxValue.add(number);
    }

    return String.format("%0" + (bits + 3)/4 + "X", number);
}

Buffer to a [ BigInteger ] hex representation

As an additional bonus answer to the related question: "How to convert byte[] to a hex string [using BigInteger ]?"

The following code will convert a byte[] to a hex String :

byte[] buffer = new byte[]{1, 2, 3};
String hex = String.format("%0" + buffer.length*2 + "X", new BigInteger(1, buffer));

Note that it is very important to set the signum argument to 1 so that BigInteger understands that the values in the array are to be parsed in as 'unsigned'.

Note also that this method creates two temporary buffers to create the string and that you might be better off parsing the array directly into a Character[] , if you are dealing with very large arrays.

You can always manually do two's complement. If the number is smaller than 0, then inverse all the bits and add one.

一个班轮(但是不要忘记考虑源的endiness问题,可以使用ByteBuffer.byteOrder处理):

new BigInteger(1, ByteBuffer.allocate(Long.SIZE/Byte.SIZE).putLong(Long.parseLong("-1")).array());

To defeat new BigInteger(long) 's interpretation of the argument as being encoded in two's complement, you can first convert the upper 63 bits of the number in a way that avoids using long 's sign bit, and then "add" the last bit:

BigInteger unsigned(long value) {
    return BigInteger
        .valueOf(value >>> 1).shiftLeft(1) // the upper 63 bits
        .or(BigInteger.valueOf(value & 1L)); // plus the lowest bit
}

Is it what you what?

public static void main(String[] args) {

    BigInteger bg =  BigInteger.valueOf(-1);        
    System.out.println(Integer.toHexString(bg.intValue()));
}

You can use this utility to convert to an unsigned integer. Since BigIntegers have unlimited size, a size must be specified to determine how much sign extension to keep in the translation:

public static BigInteger toPositive(BigInteger num, int sizeInBytes) {
    return num.andNot(BigInteger.valueOf(-1).shiftLeft(sizeInBytes * 8));
}

The simples solution is from @Ahmet Karakaya, but should be fixed for bigger numbers:

BigInteger bg =  BigInteger.valueOf(-1);
System.out.println(Long.toHexString(bg.intValue()));

Result: ffffffffffffffff

or simply:

System.out.println(Long.toHexString(-1))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM