Interpret a negative number as unsigned with BigInteger

ぐ巨炮叔叔 提交于 2020-05-29 07:13:12

问题


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.


回答1:


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;
}



回答2:


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);
}  



回答3:


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 (i.e. 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);

E.g. 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 (i.e. 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.

E.g. 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.




回答4:


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




回答5:


One Liner(however don't forget to consider endiness issues with the source, which can be handled using ByteBuffer.byteOrder):

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



回答6:


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
}



回答7:


Is it what you what?

public static void main(String[] args) {

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



回答8:


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));
}



回答9:


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))


来源:https://stackoverflow.com/questions/10886962/interpret-a-negative-number-as-unsigned-with-biginteger

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!