7

I'm constructing BigInt numbers that consist of two Longs each in the following way:

val msb = -1L // some arbitrary long value, can be anything between Long.Min/MaxValue
val lsb = 25L // a second arbitrary long value        

val bb = ByteBuffer
  .allocate(17)
  .put(0.toByte) // 1 byte
  .putLong(msb) // 8 bytes
  .putLong(lsb) // 8 bytes

val number = BigInt(bb.array) // in this case: 340282366920938463444927863358058659865

The reason I'm adding another 0-Byte at the front is to guarantee that the result is a positive number. Otherwise, the resulting BigInt could be negative due to two's complement. An algorithm that is called afterwards expects numbers greater or equal than zero.

So far, so good.

I'm having trouble with reversing this whole process - transforming the BigInt back to two Longs (exactly the two values that were used as the input). I can't just do the following:

val arr = number.toByteArray
val bb = ByteBuffer.wrap(arr)
val ignore = bb.getByte
val msb = bb.getLong
val lsb = bb.getLong

Imagine the BigInt number is e.g. 3. Then .toByteArray would result in an Array of size 1, not 16 (or 17), and therefore the calls to getLong would cause a BufferUnderflowException.

What's the easiest way to solve this problem? I tried several ways to manually fill up the buffer until there are 16 bytes available, but since this "padding" must correctly take the two's complement of the two numbers into account, I wasn't succesful.

ceran
  • 1,392
  • 1
  • 17
  • 43

4 Answers4

5

Modulo operation can be helpful here:

....
val number = BigInt(bb.array) // in this case: 340282366920938463444927863358058659865

val modulo = BigInt(2).pow(64)
val lsb2 = (number / modulo).toLong     //25
val msb2 = (number.mod(modulo)).toLong  //-1
Piotr Reszke
  • 1,576
  • 9
  • 21
  • you could also consider removing the Long in the first step and start with constructing 2 BigIntegers and doing val number2 = modulo*BigInt(243423) + BigInt(524543). (There would be no tricks with setting first bit manually - higher level programming. Not sure if this is applicable in your case) – Piotr Reszke Oct 02 '16 at 21:26
  • 1
    It took me a while to understand it mathematically and I would never have came up with this myself . But it works nicely and is concise, without having to use `ByteBuffer`. And the modulo BigInt can be reused. Thanks! – ceran Oct 04 '16 at 13:58
  • @Piotr R I wanted to upvote and i downvoted by mistake(and now i realized it ,:),if you want add a small edit to the question so i can upvote it again. – GOXR3PLUS Oct 05 '16 at 07:52
2

Using the plumbing/padding approach, and with number as defined in the question,

val msb, lsb = split(number) // (-1,25)

/** split the passed Bigint into a (msb: Long, lsb: Long) tuple */
def split(bi: BigInt) = splitArray(bi.toByteArray.takeRight(16)) // Considers only the last bytes if there are more than 16

/** assumes arrays of size 16 or less */
def splitArray(ba: Array[Byte]): (Long, Long) = (
    toLong(ba.take(ba.length - 8)), // Take the msb part: anything before the last 8 bytes (take() seems happy with negative numbers ;))
    toLong(ba.takeRight(8))         // Take at most 8 bytes from the lsb part
   ) 

/** Convert the passed byte-array to a long. Expect arrays of size 8 and less. */
def toLong(ba: Array[Byte]) = ByteBuffer.wrap(zeroPad(ba)).getLong

/** prefix the passed array with 0 bytes. Expect arrays of size 8 and less,
    returns an array of length 8. */
def zeroPad(ba: Array[Byte]) = Array.fill[Byte](8 - ba.length)(0) ++ ba 

Not as succinct as Piotr's modulo proposal, bus worth the little mental gymnastic :)

Shastick
  • 1,218
  • 1
  • 12
  • 29
1

Instead of using ByteBuffer.wrap you can just allocate a large enough ByteBuffer (i.e. of size 17 bytes) and put(byte[]) the byte array at the right position (i.e. so that it "lines up" with the lsb of the buffer) like this:

val number = BigInt("340282366920938463444927863358058659865")

val arr = number.toByteArray  // of length 0-17
val bb = ByteBuffer.allocate(17)
bb.position(1 + (16 - arr.length))
bb.put(arr)
bb.rewind()

val ignore = bb.get
val msb = bb.getLong
val lsb = bb.getLong
jkovacs
  • 3,470
  • 1
  • 23
  • 24
1

Your proposed method for extraction will work, you just have to put that leading 0-Byte to better use.

val bb = ByteBuffer
  .allocate(17)
  .put(1.toByte) // 1 byte (some positive value)
  .putLong(msb)  // 8 bytes
  .putLong(lsb)  // 8 bytes

val number = BigInt(bb.array) // never negative, always 17 bytes

val bbx = ByteBuffer.wrap(number.toByteArray)
bbx.get      // throw away
bbx.getLong  // msb
bbx.getLong  // lsb

If, for some reason, you need number to contain only msb and lsb bits, then you can create a mask to assist with the extraction.

val maskbb = ByteBuffer
  .allocate(17)
  .put(Byte.MinValue) // 1 byte
  .putLong(0L) // 8 bytes
  .putLong(0L) // 8 bytes

val arr = (BigInt(maskbb.array) + number).toByteArray
val bbx = ByteBuffer.wrap(arr)
... // the rest us unchanged
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • If you put "1" as first byte you are changing the value of number. You are adding: 2^128 = 340282366920938463463374607431768211456 which in some areas is considered a lot :) – Piotr Reszke Oct 03 '16 at 08:56
  • @PiotrR, you're right, of course, but the OP only stipulated that `number` must A) always be positive, and B) be long enough to extract `msb` and `lsb` accurately/efficiently. It's not stated that the `number` value should also be a reflection of the `msb` and `lsb` values. On the other hand, perhaps that was unstated but intended. That's why I offered the 2nd, `maskbb`, solution, which extracts `msb`/`lsb` from an unaltered `number`. – jwvh Oct 03 '16 at 15:48