Я строю числа 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 байтов, но поскольку это "заполнение" должно правильно учитывать два дополнения этих двух чисел, я не был успешным.
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
ByteBuffer
. А модуль BigInt можно использовать повторно. Спасибо!
Используя подход сантехники/дополнения и 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
Не так краток, как предложение по модулю Петра, автобус стоит маленькой умственной гимнастике:)
Ваш предлагаемый метод для извлечения будет работать, вам просто нужно использовать этот ведущий 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
number
должен A) всегда будет положительным, и B) достаточно долго , чтобы извлечь msb
и lsb
точно / эффективно. Это не указано , что number
значение также должно быть отражением msb
и lsb
значений. С другой стороны, возможно, это было неустановлено, но предполагалось. Именно поэтому я предложил 2 - ю, maskbb
, раствор, который извлекает msb
/ lsb
от неизмененной number
.
Вместо 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
bb.getByte.getLong
работает. Развеbb.getByte
не возвращает 0, что приводит к0.getLong
?