C# equivalent of a javascript TypedArray created from an existing array's .buffer?

10.6k views Asked by At

I am trying to convert a javascript function to a C# script. One of the things the javascript version does is create a Uint32Array from an existing Float32Array's .buffer

Does anyone know what the equivalent of this would be in C#? I am NOT talking about what Float32Array and Uint32Array are in C# I am talking about the way that the javascript typed array gets initialized using the buffer from the dst variable (see code)... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray Its important because other arrays get initialized later on after this function using (for example) var dstUint32 = new Uint32Array(dst.buffer)...

This is the code (src is an existing Float32Array - see a bit of the initial values for this below)...

compileClassifier = function(src, width, scale, dst) {
        width += 1;
        if (!dst) dst = new Float32Array(src.length);
        var dstUint32 = new Uint32Array(dst.buffer);

        dstUint32[0] = src[0];
        dstUint32[1] = src[1];
        var dstIndex = 1;
        for (var srcIndex = 1, iEnd = src.length - 1; srcIndex < iEnd; ) {
            dst[++dstIndex] = src[++srcIndex];

            var numComplexClassifiers = dstUint32[++dstIndex] = src[++srcIndex];
            for (var j = 0, jEnd = numComplexClassifiers; j < jEnd; ++j) {

                var tilted = dst[++dstIndex] = src[++srcIndex];
                var numFeaturesTimes3 = dstUint32[++dstIndex] = src[++srcIndex] * 3;
                if (tilted) {
                    for (var kEnd = dstIndex + numFeaturesTimes3; dstIndex < kEnd; ) {
                        dstUint32[++dstIndex] = src[++srcIndex] + src[++srcIndex] * width;
                        dstUint32[++dstIndex] = src[++srcIndex] * (width + 1) + ((src[++srcIndex] * (width - 1)) << 16);
                        dst[++dstIndex] = src[++srcIndex];
                    }
                } else {
                    for (var kEnd = dstIndex + numFeaturesTimes3; dstIndex < kEnd; ) {
                        dstUint32[++dstIndex] = src[++srcIndex] + src[++srcIndex] * width;
                        dstUint32[++dstIndex] = src[++srcIndex] + ((src[++srcIndex] * width) << 16);
                        dst[++dstIndex] = src[++srcIndex];
                    }
                }

                var inverseClassifierThreshold = 1 / src[++srcIndex];
                for (var k = 0; k < numFeaturesTimes3; ) {
                    dst[dstIndex - k] *= inverseClassifierThreshold;
                    k += 3;
                }

                if (inverseClassifierThreshold < 0) {
                    dst[dstIndex + 2] = src[++srcIndex];
                    dst[dstIndex + 1] = src[++srcIndex];
                    dstIndex += 2;
                } else {
                    dst[++dstIndex] = src[++srcIndex];
                    dst[++dstIndex] = src[++srcIndex];
                }
            }
        }
        dst = dst.subarray(0, dstIndex + 1);
        return dst;
    }

The src variable in there is created from this (this is a very cut down version the full version is thousands of numbers long):-

var classifier = [20,20,0.8226894140243530,3,0,2,3,7,14,4,-1.,3,9,14,2,2.,4.0141958743333817e-003,0.0337941907346249,0.8378106951713562,0,2,1,2,18,4,-1.,7,2,6,4,3.,0.0151513395830989,0.1514132022857666,0.7488812208175659,0,2,1,7,15,9,-1.,1,10,15,3,3.,4.2109931819140911e-003,0.0900492817163467,0.6374819874763489,6.9566087722778320,16,0,2,5,6,2,6,-1.,5,9,2,3,2.,1.6227109590545297e-003,0.0693085864186287];
src = new Float32Array(classifier);

This is what gets logged in the console for dstUint32 and dst respectively with those numbers set as src (the function doesn't work with just those numbers since as I say the actual array is far, far longer) Its interesting that almost none of the numbers match the original array...

[20, 20, 1062378438, 3, 0, 6, 1641, 61341710, 3279494571, 2109, 30670862, 1140399531, 1024093127, 1062632131, 0, 6, 469, 61341714, 3263430756, 475, 61341702, 1128661142, 1041959952, 1061140142, 0, 6, 1639, 138018831, 3278731586, 2341, 46006287, 1144134386, 1035496386, 1059271173, 1088330890, 16, 0, 6, 1409, 92012546, 3290042412, 2111, 46006274, 1150947372, 1032712617, 4294967295, 4294967295, 0, 4294967295, 4294967295, 4294967295, 0, 4294967295, 4294967295, 4294967295, 0, 4294967295, 4294967295, 4294967295, 0, 4294967295, 4294967295, 4294967295, 0, 4294967295]


[2.802596928649634e-44, 2.802596928649634e-44, 0.822689414024353, 4.203895392974451e-45, 0, 8.407790785948902e-45, 2.2995307799570248e-42, 9.874165102541455e-37, -249.1158905029297, 2.955338461261039e-42, 7.787657921469055e-38, 498.2317810058594, 0.03379419073462486, 0.8378106951713562, 0, 8.407790785948902e-45, 6.572089797683392e-43, 9.874168689865524e-37, -66.00076293945312, 6.656167705542881e-43, 9.874157927893318e-37, 198.00228881835938, 0.1514132022857666, 0.7488812208175659, 0, 8.407790785948902e-45, 2.2967281830283752e-42, 5.597240788537616e-34, -237.47366333007812, 3.280439704984397e-42, 2.7918024463192472e-37, 712.4210205078125, 0.09004928171634674, 0.6374819874763489, 6.956608772277832, 2.2420775429197073e-44, 0, 8.407790785948902e-45, 1.9744295362336673e-42, 1.1848984491218286e-35, -616.252685546875, 2.958141058189689e-42, 2.7917995316184414e-37, 1232.50537109375, 0.06930858641862869, NaN, NaN, 0, NaN, NaN, NaN, 0, NaN, NaN, NaN, 0, NaN, NaN, NaN, 0, NaN, NaN, NaN, 0, NaN]

So you can see here that the code

dstUint32[0] = src[0];
dstUint32[1] = src[1];

appears to have set BOTH dstUint32[0]/dstUint32[1] AND dst[0]/dst[1] to something, just that in dst[0]/dst[1] the value is getting logged as 2.802596928649634e-44 rather than 20?

1

There are 1 answers

11
Ben On
  • The equivalent of a js UInt32Array is UInt32[].
  • The equivalent of a js Float32Array is float[].
  • The equivalent of an ArrayBuffer is byte[].

JavaScript typed arrays are views onto the array buffer. Thus the element in the UInt32Array is not cast to a float, rather the contents of the physical storage - the bits - are reinterpreted as a float.

To do this in C# you need to do one of a couple of things.

If you care about sharing the arraybuffer

If you care about sharing the arraybuffer, i.e. you need change in one array to show up in the other array immediately, then you need to implement the whole thing. C# doesn't offer this out of the box. In order to achieve it you will need to use the InteropServices.Marshal facilities. These will allow you to get access to the underlying memory.

If you don't care about sharing the arraybuffer

If you just want to convert in one direction, then maybe later convert back again, but you don't need changes to be visible immediately, or to use a shared ArrayBuffer, you have an easier job.

  • Create a MemoryStream,
  • write the Float32 elements from the array
  • read the UInt32 elements

Something like this:

    static byte[] ReinterpretAsByteArray(UInt32[] a)
    {
        using (MemoryStream s = new MemoryStream())
        {
            using (BinaryWriter w = new BinaryWriter(s, Encoding.Unicode, true))
            {
                for (int i = 0; i < a.Length; i++)
                {
                    w.Write(a[i]);
                }
            }
            return s.ToArray();
        }
    }
    static float[] ReinterpretAsFloatArray(byte[] b) {
        using (MemoryStream s = new MemoryStream(b, false)) {
            using (BinaryReader r = new BinaryReader(s, Encoding.Unicode, true))
            {
                float[] f = new float[b.Length / 4]; // 4 = sizeof float
                for (int i = 0; i < b.Length; i++)
                {
                    f[i] = r.ReadSingle();
                }
                return f;
            }
        }
    }    

You will need to write a similar function to convert a byte array to a Uint32 array, and to convert a float array to a byte array, but that is a matter of changing two lines.