How to convert last 4 bytes in an array to an integer?
Asked Answered
D

8

6

If I have an Uint8Array array in JavaScript, how would I get the last four bytes and then convert that to an int? Using C# I would do something like this:

int count = BitConverter.ToInt32(array, array.Length - 4);

Is there an inequivalent way to do this using JavaScript?

Depreciatory answered 19/2, 2013 at 17:1 Comment(4)
@cIph3r: YesVermicide
@Vermicide ok, but this only works in firefoxRodarte
@clph3r, And Chrome, and Safari, and Opera, and IE10, and Android....Ambrotype
Old question, I know, but that C# method returns a signed integer. Many of the answers here are returning unsigned integers. Be aware and careful.Mellen
A
12

Access the underlying ArrayBuffer and create a new TypedArray with a slice of its bytes:

var u8 = new Uint8Array([1,2,3,4,5,6]); // original array
var u32bytes = u8.buffer.slice(-4); // last four bytes as a new `ArrayBuffer`
var uint = new Uint32Array(u32bytes)[0];

If the TypedArray does not cover the entire buffer, you need to be a little trickier, but not much:

var startbyte = u8.byteOffset + u8.byteLength - Uint32Array.BYTES_PER_ELEMENT;
var u32bytes = u8.buffer.slice(startbyte, startbyte + Uint32Array.BYTES_PER_ELEMENT);

This works in both cases.

If the bytes you want fit in the alignment boundary of your underlying buffer for the datatype (e.g., you want the 32-bit value of bytes 4-8 of the underlying buffer), you can avoid copying the bytes with slice() and just supply a byteoffset to the view constructor, as in @Bergi's answer.

Below is a very-lightly-tested function that should get the scalar value of any offset you want. It will avoid copying if possible.

function InvalidArgument(msg) {
    this.message = msg | null;
}

function scalarValue(buf_or_view, byteOffset, type) {
    var buffer, bufslice, view, sliceLength = type.BYTES_PER_ELEMENT;
    if (buf_or_view instanceof ArrayBuffer) {
        buffer = buf_or_view;
        if (byteOffset < 0) {
            byteOffset = buffer.byteLength - byteOffset;
        }
    } else if (buf_or_view.buffer instanceof ArrayBuffer) {
        view = buf_or_view;
        buffer = view.buffer;
        if (byteOffset < 0) {
            byteOffset = view.byteOffset + view.byteLength + byteOffset;
        } else {
            byteOffset = view.byteOffset + byteOffset;
        }
        return scalarValue(buffer, view.byteOffset + byteOffset, type);
    } else {
        throw new InvalidArgument('buf_or_view must be ArrayBuffer or have a .buffer property');
    }
    // assert buffer instanceof ArrayBuffer
    // assert byteOffset > 0
    // assert byteOffset relative to entire buffer
    try {
        // try in-place first
        // only works if byteOffset % slicelength === 0
        return (new type(buffer, byteOffset, 1))[0]
    } catch (e) {
        // if this doesn't work, we need to copy the bytes (slice them out)
        bufslice = buffer.slice(byteOffset, byteOffset + sliceLength);
        return (new type(bufslice, 0, 1))[0]
    }
}

You would use it like this:

// positive or negative byte offset
// relative to beginning or end *of a view*
100992003 === scalarValueAs(u8, -4, Uint32Array)
// positive or negative byte offset
// relative to the beginning or end *of a buffer*
100992003 === scalarValue(u8.buffer, -4, Uint32Array)
Ambrotype answered 19/2, 2013 at 17:20 Comment(1)
+1, very nice. Yet it won't work if u8 is a view with an offset on its buffer, that would need to be handled special.Vermicide
O
8

Do you have an example? I think this would do it:

var result = ((array[array.length - 1]) | 
              (array[array.length - 2] << 8) | 
              (array[array.length - 3] << 16) | 
              (array[array.length - 4] << 24));
Overprize answered 19/2, 2013 at 17:11 Comment(7)
using [255,255,255,255] will result in -1 using this code for some reason.Bertsche
@Bertsche The result is -1 because its signed. Add an Unsigned Right Shift Operator >>> 0 to convert the result to an unsigned value (4294967295).Barrelhouse
hmm, that makes sense.Bertsche
This assumes the byte array is big-endian, correct? If so, why did you give the big-endian solution when most processors are little-endian?Broglie
@Broglie it's also known as network order and is used widely for packets and file formats.Laterite
@Barrelhouse thanks for your comment, it made me realized I missed this operator in my codeHyperdulia
If array is a binary string, use array.charCodeAt(n) ... insteadReadability
C
7

Nowadays if you can live with IE 11+ / Chrome 49+ / Firefox 50+, then you can use DataView to make your life almost as easy as in C#:

var u8array = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]); // -1
var view = new DataView(u8array.buffer)
console.log("result:" + view.getInt32());

Test it here: https://jsfiddle.net/3udtek18/1/

Chromatograph answered 23/1, 2017 at 21:26 Comment(0)
K
5

I'm surprised that the other answers don't use the native Buffer object, which provides a lot of this tooling in a simple, native library. It's possible that this library isn't widely used for bitpacking/unpacking simply because people don't think to check here and it took me a while to find it, too, but it's the right tool for bitpacking/unpacking in nodejs/javascript/typescript.

You can use it like so:

// Create a simple array with 5 elements. We'll pop the last 4 and you should expect the end value to be 1 because this is a little-endian array with all zeros other than the 1 in the littlest(?)-endian
const array = [0, 1, 0, 0, 0]

// get the last 4 elements of your array and convert it to a Buffer
const buffer = Buffer.from(array.slice(-4));

// Use the native Buffer type to read the object as an (U) unsigned (LE) little-endian 32 (32 bits) integer
const value = Buffer.readUInt32LE();

Or, more concisely:

const value = Buffer.from(array.slice(-4)).readUInt32LE();
Kashmir answered 2/4, 2021 at 4:44 Comment(0)
S
4

A little inelegant, but if you can do it manually based on the endianess.

Little endian:

var count = 0;
// assuming the array has at least four elements
for(var i = array.length - 1; i >= array.length - 4; i--)
{
    count = count << 8 + array[i];
}

Big endian:

var count = 0;
// assuming the array has at least four elements
for(var i = array.length - 4; i <= array.length - 1 ; i++)
{
    count = count << 8 + array[i];
}

This can be extended to other data lengths

Edit: Thanks to David for pointing out my typos

Silvester answered 19/2, 2013 at 17:12 Comment(2)
should be .length not .LengthImpassioned
Why do none of the other answers deal with the distinction between little-endian and big-endian? They confused me at first because I needed a little-endian solution, but all of theirs appear to be big-endian. (which is odd since most processors use little-endian) Anyway, thanks for the answer.Broglie
V
1

It should be more efficient to just create an Uint32Array view on the same ArrayBuffer and accessing the 32-bit number directly:

var uint8array = new Uint8Array([1,2,3,4,5,6,7,8]);
var uint32array = new Uint32Array(
                    uint8array.buffer,
                    uint8array.byteOffset + uint8array.byteLength - 4,
                    1 // 4Bytes long
                  );
return uint32array[0];
Vermicide answered 19/2, 2013 at 17:14 Comment(2)
Unfortunately, it seems you can't create a new view of a different type on the same buffer unless the second argument is a multiple the new view's length. E.g. on an 8-byte buffer this fails even though it seems like an arbitrary restriction: new Uint32Array(buffer, 2, 1)Ambrotype
@FrancisAvila: Thanks for the hint, in case of a non-4Byte-multiple buffer one would need to slice that. I fixed my code now (the above works), I forgot that I needed to subtract 4 from the byteLength to get 32 bits :-/Vermicide
R
0
var a = Uint8Array(6)
a.set([1,2,8,0,0,1])

 i1 = a[a.length-4];
 i2 = a[a.length-3];
 i3 = a[a.length-2];
 i4 = a[a.length-1];

console.log(i1<<24 | i2<<16 | i3<<8 | i4);
Rodarte answered 19/2, 2013 at 17:22 Comment(0)
B
0

It's a shame there are not build in ways to do this. I needed to read variables of variable sizes so based on Imortenson answer I've wrote this little function where p is read position and s is number of bytes to read:

function readUInt(arr, p, s) {
    var r = 0;
    for (var i = s-1; i >= 0; i--) {
        r |= arr[p + i] << (i * 8);
    } return r >>> 0;
} 

var iable = readUint(arr, arr.length - 4, 4);
Bloated answered 31/1, 2019 at 3:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.