berry_burr
berry_burr

Reputation: 351

Convert hex to BigInteger

I am trying to convert hex to Big Integer. Basically I have 32 characters = 16 bytes, so I expect that BigInteger also has 16 bytes, but for some cases, i.e. hex starts with 99.. it generates additional byte with 0. I'm using

new BigInteger(hex, 16)

How can I avoid the 17th byte?

Upvotes: 3

Views: 3048

Answers (4)

Tagir Valeev
Tagir Valeev

Reputation: 100169

Seems that you are using BigInteger for the sole purpose to convert the hex string of fixed length to the byte[] array. This can be done in another way, for example, using ByteBuffer class:

static byte[] toByteArray(String s) {
    ByteBuffer bb = ByteBuffer.allocate(16);
    bb.asIntBuffer().put((int) Long.parseLong(s.substring(0, 8), 16))
                    .put((int) Long.parseLong(s.substring(8, 16), 16))
                    .put((int) Long.parseLong(s.substring(16, 24), 16))
                    .put((int) Long.parseLong(s.substring(24), 16));
    return bb.array();
}

Or somewhat simpler in Java-8 using Long.parseUnsignedLong():

static byte[] toByteArray8(String s) {
    ByteBuffer bb = ByteBuffer.allocate(16);
    bb.asLongBuffer().put(Long.parseUnsignedLong(s.substring(0, 16), 16))
                     .put(Long.parseUnsignedLong(s.substring(16), 16));
    return bb.array();
}

This way you should not care about corner cases (you will always get 16 bytes even for "000...000" string) and likely will have less heap allocations. Sample usage:

System.out.println(Arrays.toString(toByteArray("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")));
// [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
System.out.println(Arrays.toString(toByteArray("80000000000000000000000000000000")));
// [-128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println(Arrays.toString(toByteArray("123FFFFFFFFFFFFFFFFFFFFFFFFFFFFF")));
// [18, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
System.out.println(Arrays.toString(toByteArray("007FFFFFFFFFFFFFFFFFFFFFFFFFFFFF")));
// [0, 127, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
System.out.println(Arrays.toString(toByteArray("00000000000000000000000000000001")));
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]

Upvotes: 0

Anonymous Coward
Anonymous Coward

Reputation: 3200

From BigInteger's javadoc :

Immutable arbitrary-precision integers. All operations behave as if BigIntegers were represented in two's-complement notation (like Java's primitive integer types).

And description of the constructor you are using :

Translates the String representation of a BigInteger in the specified radix into a BigInteger. The String representation consists of an optional minus or plus sign followed by a sequence of one or more digits in the specified radix. The character-to-digit mapping is provided by Character.digit. The String may not contain any extraneous characters (whitespace, for example).

This means that if you call it with new BigInteger( "99000000000000000000000000000000", 16) you will get a BigInteger which holds that value (which is a positive value) as if it were represented in two's-complement notation. That positive value in two's complement does not fit in 16 bytes so of course the end result is 17 bytes long.

You are guaranteed to get a BigInteger with a maximun of 16 bytes if you call it with values between (both included):

 - new BigInteger( "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
 - new BigInteger("-80000000000000000000000000000000", 16)

Any value higher than the first or lower than the last will result in more than 16 bytes.

Upvotes: 2

Tamas Hegedus
Tamas Hegedus

Reputation: 29906

The first byte cannot start with an 1 bit, because that would mean a negative number. They prevent this by adding an additional zero byte at the start of the array. This function will check and chop off that byte:

public static byte[] signedToUnsignedBytes(byte[] myBytes) {
    return myBytes.length > 1 && myBytes[0] == 0
        ? Arrays.copyOfRange(myBytes, 1, myBytes.length)
        : myBytes;
}

Upvotes: 1

Kayaman
Kayaman

Reputation: 73528

A byte value of over 127 cannot be represented with Java's byte, since they're signed (well, they can but Java will consider them as negative numbers).

When BigInteger converts the value to a byte array, it will add a 0 byte in front to distinguish a positive value from a negative value.

This results in the fact that 128 will become [0][-128], whereas -128 will become just [-128].

If you intend to store the resulting bytes as an unsigned 128-bit value, you can just chop off the first element of the array, e.g. byte[] sanitizedBytes = Arrays.copyOfRange(myBytes, 1, 16);.

Upvotes: 0

Related Questions