Извлечение длинных из ByteBuffer (Java / Scala)

7

Я строю числа BigInt, которые состоят из двух Long каждого из следующих способов:

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

Причина, по которой я добавляю еще один 0- Byte на фронт, - гарантировать, что результатом будет положительное число. В противном случае результат BigInt может быть отрицательным из-за двух дополнений. Алгоритм, который вызывается впоследствии, ожидает, что числа больше или равны нулю.

До сих пор так хорошо.

У меня возникли проблемы с реверсированием всего этого процесса - преобразование BigInt обратно в два Long (точно два значения, которые использовались в качестве ввода). Я не могу просто сделать следующее:

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

Представьте, что число BigInt равно, например. 3. Тогда .toByteArray приведет к массиву размера 1, а не 16 (или 17), и поэтому вызовы getLong вызовут BufferUnderflowException.

Какой самый простой способ решить эту проблему? Я попробовал несколько способов вручную заполнить буфер до тех пор, пока не будет доступно 16 байтов, но поскольку это "заполнение" должно правильно учитывать два дополнения этих двух чисел, я не был успешным.

  • 0
    Вы пытаетесь закодировать BigDecimal как?
  • 0
    Извините за мое невежество, не зная scala, но я сомневаюсь, что bb.getByte.getLong работает. Разве bb.getByte не возвращает 0, что приводит к 0.getLong ?
Показать ещё 1 комментарий
Теги:
arrays
bytebuffer

4 ответа

5
Лучший ответ

Modulo может быть здесь полезной:

....
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
  • 0
    Вы также можете рассмотреть удаление Long в первом шаге и начать с создания 2 BigIntegers и выполнения val number2 = modulo * BigInt (243423) + BigInt (524543). (Не было бы хитрости с установкой первого бита вручную - программирование более высокого уровня. Не уверен, применимо ли это в вашем случае)
  • 1
    Мне потребовалось некоторое время, чтобы понять это математически, и я бы никогда не придумал это сам. Но это работает хорошо и кратко, без использования ByteBuffer . А модуль BigInt можно использовать повторно. Спасибо!
Показать ещё 2 комментария
2

Используя подход сантехники/дополнения и number, как определено в вопросе,

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 

Не так краток, как предложение по модулю Петра, автобус стоит маленькой умственной гимнастике:)

1

Ваш предлагаемый метод для извлечения будет работать, вам просто нужно использовать этот ведущий 0-байт для лучшего использования.

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

Если по какой-то причине вам нужно number содержать только msb и lsb биты, тогда вы можете создать маску для помощи при извлечении.

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
  • 0
    Если вы ставите «1» в качестве первого байта, вы меняете значение числа. Вы добавляете: 2 ^ 128 = 340282366920938463463374607431768211456, что в некоторых областях считается много :)
  • 0
    @PiotrR, вы правы, конечно, но только ОП предусмотрено , что number должен A) всегда будет положительным, и B) достаточно долго , чтобы извлечь msb и lsb точно / эффективно. Это не указано , что number значение также должно быть отражением msb и lsb значений. С другой стороны, возможно, это было неустановлено, но предполагалось. Именно поэтому я предложил 2 - ю, maskbb , раствор, который извлекает msb / lsb от неизмененной number .
1

Вместо ByteBuffer.wrap вы можете просто allocate достаточно большой ByteBuffer (т.е. размер 17 байт) и put(byte[]) массив байтов в правильном положении (то есть так, чтобы он "выравнивался" с помощью lsb буфера) следующим образом:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню