Yves Goergen Ответов: 3

Декодирование массива байтов в целое число со знаком до 64 бит в javascript


Я знаю, что JavaScript не может точно представлять все 64 - битные целые числа. Но он может точно представлять числа, превышающие 32 бита. И это то, что мне нужно. С той точностью, которую может дать мне JavaScript.

У меня есть массив байтов известной длины. Он имеет 1, 2, 4, 8 или 16 байт. И он может содержать целое число со знаком или без знака, Я знаю, что это такое. Данные являются big-endian (сетевой порядок байтов).

Как я могу получить числовое значение из этого байтового массива?

Существуют простые решения с умножением и сложением, которые полностью терпят неудачу на отрицательных числах. Есть DataView, который не может помочь с более чем 32 битами. Я заинтересован в хорошем, простом и предпочтительно эффективном чистом JavaScript-решении для решения этой проблемы. И, к сожалению, я не могу придумать такого способа обработки отрицательных двухкомпонентных значений.

Что я уже пробовал:

Я пробовал его для положительных значений:

function readInt(array) {
    var value = 0;
    for (var i = 0; i < array.length; i++) {
        value = (value * 256) + array[i];
    }
    return value;
}


Но он, очевидно, терпит неудачу на отрицательных числах, вместо этого давая мне беззнаковые значения.

Richard MacCutchan

Если массив уже содержит число (подписанное или нет), как вы думаете, что на самом деле делает ваш код?

Yves Goergen

Код декодирует большое конечное целое число без знака в его текущей форме. Он не декодирует целое число со знаком. Например, -200 как int16 кодируется как FF 38, и этот код затем декодирует его до 65336.

Richard MacCutchan

Вы должны проверить первый байт, чтобы увидеть, является ли он отрицательным значением, и если да, то преобразовать каждый байт в его противоположный знак, как вы их обрабатываете. Затем вам нужно повторно преобразовать конечное значение в отрицательное по мере необходимости.

3 Ответов

Рейтинг:
20

Yves Goergen

Эта страница объясняет хорошее и простое решение:

• Сложите все биты с правого конца, вплоть до второго по значению бита.
• Самый значительный бит добавляется не как 2^i, а как -2^i.

Мой код работает в большей области, которая имеет массив и pos для чтения.

function readInt(size) {
	var value = 0;
	var first = true;
	while (size--) {
		if (first) {
			let byte = array[pos++];
			value += byte & 0x7f;
			if (byte & 0x80) {
				value -= 0x80;
				// Treat most-significant bit as -2^i instead of 2^i
			}
			first = false;
		}
		else {
			value *= 256;
			value += array[pos++];
		}
	}
	return value;
}


Необработанные байты предоставляются в array (ля Uint8Array) и pos это следующий индекс для чтения. Эта функция начинает считывать данные в текущий момент времени. pos и авансы pos как это читает один из size байты.


Рейтинг:
0

Jochen Arndt

При упаковке байтовых данных в более широкое целое число все байты должны рассматриваться как беззнаковые, а все операции должны выполняться без знака.

Это требует использования побитовых операций:

for (var i = 0; i < array.length; i++) {
    value = (value << 8) | array[i];
}
Но это ограничено 32-битными значениями, потому что JavaScript игнорирует более высокие биты с побитовыми операциями.

Возможным решением (я его еще не тестировал) было бы сделать значения без знака:
for (var i = 0; i < array.length; i++) {
    value *= 256;
    if (array[i] < 0) {
        value += 256 + array[i];
    } else {
        value += array[i];
    }
}
Хитрость здесь заключается в том, что значения массива являются байтами в диапазоне от -128 до +127. Если значение байта отрицательно, добавляется 256, чтобы сделать его соответствующим беззнаковым значением.

Он должен работать до 63 бит, когда операции выполняются с использованием целых чисел. Если интерпретатор JavaScript решит выполнять операции с плавающей запятой внутренне, результат станет неточным с более чем 53 битами.

Таким образом, нет никакого решения (насколько я знаю) для 128 бит (16 байт).

[РЕДАКТИРОВАТЬ]
Поскольку здесь вам нужны целочисленные операции без ошибок округления, вы не можете ожидать правильных результатов для конечных значений больше, чем Number.MAX_SAFE_INTEGER - JavaScript | MDN[^] который ограничен 53 битами (число битов мантиссы для значений с плавающей запятой двойной точности).
[/РЕДАКТИРОВАТЬ]


Yves Goergen

ЭМ, извините, это Uint8Array, так что никаких отрицательных байтов здесь нет.

Yves Goergen

И глупый я, длина массива может быть только 1, 2, 4 или 8, а не 16. Ваш код не работает для меня. Когда я заменяю '< 0' на '>= 0x80', он выдает еще более высокие положительные значения, более длинные, чем это возможно с 16 битами.

Jochen Arndt

Когда значения равны Uint8, не должно быть никакой необходимости проверять наличие значений < 0 (я просто действовал в соответствии с "он может содержать целое число со знаком или без знака").

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

Для 64-битных значений интерпретатор, вероятно, переключится на внутреннюю плавающую точку. Однако вы можете попробовать использовать 32-битный код дважды для верхнего и нижнего битов и объединить их впоследствии с помощью умножения и сложения. Но я думаю, что это будет сделано снова как плавающая точка.

Yves Goergen

Не все байты отрицательны. Байты никогда не бывают отрицательными, они варьируются от 0 до 255. Полное целое число отрицательно. И у меня есть байты, которые представляют это число.

Jochen Arndt

Да, я совсем забыл об этом деле.

Полное число отрицательно, когда установлен MSB первого байта. Решение будет заключаться в установке всех старших битов на один, если это произойдет. Для первого (32-битного) кода это можно сделать, установив начальное значение равным -1 (все биты установлены), когда установлен MSB первого байта.

Рейтинг:
0

Member 9273009

Вот мое решение этой проблемы:

// calculate 64-bit long in base 10
// bitwise operators stop working after 32 bits in JS 
// so we must use standard arithmetic operators:
var ret = 0;
if (bytes[7] >= 128) { // negative number
    bytes.forEach((val, i) => { ret += (255 - val) * 256 ** i });
    ret = ret * -1 - 1;
}
else bytes.forEach((val, i) => { ret += val * 256 ** i; });

Где "байты" - это Uint8Array размера 8 (сериализованный 64-битный). Имейте в виду, что тип номера JS не имеет того же диапазона, что и типичные 64-битные числа, такие как long/unsigned long. В самых высоких/самых низких диапазонах вы можете увидеть проблемы округления или JS разрешит это до "бесконечности".