2

I am trying to convert some Java code into JavaScript which is needed for the application I am working on. I am stuck on one class and its methods that convert a variable of type double to long, and then long to a byte array that consists of 8 bytes representing that long number. The Java code is as follows:

 public static byte[] doubleToByteArray(double number)
   {
     // double to long representation
     long longNum = Double.doubleToLongBits(number);

     // long to 8 bytes
     return new byte[] {(byte)((longNum >>> 56) & 0xFF),
                     (byte)((longNum >>> 48) & 0xFF),
                     (byte)((longNum >>> 40) & 0xFF),
                     (byte)((longNum >>> 32) & 0xFF),
                     (byte)((longNum >>> 24) & 0xFF),
                     (byte)((longNum >>> 16) & 0xFF),
                     (byte)((longNum >>>  8) & 0xFF),
                     (byte)((longNum >>>  0) & 0xFF)};
  }  // end doubleToByte(.)

How would I go about doing this in JavaScript? My first issue is with the doubleToLongBits method. Does anything similar exist in JavaScript? Furthermore, how to cast a variable into a Byte?

Thank you in advance.

Ciprian Tomoiagă
  • 3,773
  • 4
  • 41
  • 65
elbereth
  • 37
  • 1
  • 4
  • 10

3 Answers3

5

Based on Brian's answer and on your desired representation, you can use the typed array as follows:

function doubleToByteArray(number) {
    var buffer = new ArrayBuffer(8);         // JS numbers are 8 bytes long, or 64 bits
    var longNum = new Float64Array(buffer);  // so equivalent to Float64

    longNum[0] = number;

    return Array.from(new Int8Array(buffer)).reverse();  // reverse to get little endian
}

function interactiveExample() {
    var input = parseFloat(document.getElementById('input').value);
    var output = document.getElementById('output');
    var result = doubleToByteArray(input);

    output.innerHTML = '[' + result[0];
    for (var i = 1; i < result.length; i++) {
        output.innerHTML += ', ' + result[i];
    }
    output.innerHTML += ']';
}

document.getElementById('input').value = Math.PI;
interactiveExample();
<input type="number" id="input" step="0.01" onchange="interactiveExample()" />
<div id="output"></div>
Community
  • 1
  • 1
Ciprian Tomoiagă
  • 3,773
  • 4
  • 41
  • 65
1

You can use typed arrays and an ArrayBuffer to accomplish this. I'm no expert on floating-point representation, but this should do what you need:

function doubleToByteArray(number) {
    var buffer = new ArrayBuffer(4);
    var intView = new Int32Array(buffer);
    var floatView = new Float32Array(buffer);

    floatView[0] = number;

    // Debug: display binary representation of `number`
    // console.log(intView[0].toString(2));
    return [
        (intView[0] >> 24) & 0xFF,
        (intView[0] >> 16) & 0xFF,
        (intView[0] >>  8) & 0xFF,
        (intView[0] >>  0) & 0xFF,
        (intView[1] >> 24) & 0xFF,
        (intView[1] >> 16) & 0xFF,
        (intView[1] >>  8) & 0xFF,
        (intView[1] >>  0) & 0xFF
    ];
}

function interactiveExample() {
    var input = parseFloat(document.getElementById('input').value);
    var output = document.getElementById('output');
    var result = doubleToByteArray(input);

    output.innerHTML = '[' + result[0];
    for (var i = 1; i < result.length; i++) {
        output.innerHTML += ', ' + result[i];
    }
    output.innerHTML += ']';
}

document.getElementById('input').value = Math.PI;
interactiveExample();
<input type="number" id="input" step="0.01" onchange="interactiveExample()" />
<div id="output"></div>
Claudia
  • 1,197
  • 15
  • 30
Brian S
  • 4,878
  • 4
  • 27
  • 46
  • Unfortunately, 2 codes seem to produce different outputs. The Java version gives the following as an output for PI: 64,9,33,-5,84,68,45,24. The JavaScript version you suggested gives:64, 73, 15, 219, 64, 73, 15, 219. Thank you for the effort though. – elbereth Sep 20 '14 at 06:54
  • Hrm, I'm not certain where I've gone wrong. Even using `Float64Array`, the Java version seems to produce a binary representation that's twice as long (although the low bits are the same), and using the binary representation from the Javascript does not produce the same result, even if the Java code is switched from `long` to `int`. Sorry. :( – Brian S Sep 20 '14 at 21:08
  • The Java code uses `double` which is a 64-bit float. In your code, you'd need to use `Float64Array` instead of `Float32Array`. You can also using `Uint8Array` instead of `Int32Array` to avoid shifting and masking. – Drew Noakes Oct 11 '17 at 18:45
0

You might look at JavaScript Exact Arithmetic. This site implements, among many things, conversion of IEEE Double to ByteArray and ByteArray to IEEE Double. IEEE Double is the representation used by Java. So, a byte array produced by this site will convert to the exact double using Java.

I have extracted the underlying code and build up the library JRS (for J R Stockton, the author of the original code). The JRS library offers two methods:

  • doubleToHexString(): converts an IEEE Double to an hexadecimal string
  • hexStringToDouble(): converts an hexadecimal string to an IEEE Double

The library is provided below. Please take a look at the Copyright page for fair usage.

The JRS library is as follows:

/**
 * A library that allow conversion of double to byteArray and vis versa.
 * Extracted from "JRS - JavaScript Exact Arithmetic - J R Stockton (See
 * http://www.merlyn.demon.co.uk/js-exact.htm#DW4 and 
 * http://www.merlyn.demon.co.uk/contents.htm#Copy).
 */

JRS = function(){
    function numberToBinString(number, binStringLength) {
        var A = [], T = null; // number>=0
        while (binStringLength--) {
            T = number % 2;
            A[binStringLength] = T;
            number -= T;
            number /= 2;
        }
        return A.join("");
    }

    function HexFn(fourBitsBinString) {
        return parseInt(fourBitsBinString, 2).toString(16);
    }

    function binStringToHexString(binString) {
        return binString.replace(/(\d{4})/g, HexFn );
    }

    function hexStringToBinString(hexString) {
        var binString = "";

        for(var i=0; i< hexString.length-1; i+=2) {
            binString += numberToBinString(parseInt(hexString.substr(i, 2), 16), 8);
        }

        return binString;    
    }

    function SngFwd(Sg, Ex, Mt) {
        var B = {};
        Mt = Math.pow(2, 23) * Mt + 0.5; // round
        B.a = 0xFF & Mt;
        B.b = 0xFF & (Mt >> 8);
        B.c = 0x7F & (Mt >> 16) | (Ex & 1) << 7;
        B.d = Sg << 7 | (Ex >> 1);
        return B;
    }

    function DblFwd(Sg, Ex, Mt) {
        var B = {};
        Mt = Math.pow(2, 52) * Mt;
        B.a = 0xFFFF & Mt;
        B.b = 0xFFFF & (Mt >> 16);
        Mt /= Math.pow(2, 32); // Integers are only 32-bit
        B.c = 0xFFFF & Mt;
        B.d = Sg << 15 | Ex << 4 | 0x000F & (Mt >> 16);
        return B;
    }

    function CVTFWD(NumW, Qty) { // Function now without side-effects
        var Sign = null, Expo = null, Mant = null, Bin = null, nb01 = ""; // , OutW = NumW/4
        var Inf = {
            32 : {d: 0x7F, c: 0x80, b: 0, a : 0},
            64 : {d: 0x7FF0, c: 0, b: 0, a : 0}
        };
        var ExW = {32: 8, 64: 11}[NumW], MtW = NumW - ExW - 1;

        if (isNaN(Qty)) {
            Bin = Inf[NumW];
            Bin.a = 1;
            Sign = false;
            Expo = Math.pow(2, ExW) - 1;
            Mant = Math.pow(2, -MtW);
        }

        if (!Bin) {
            Sign = Qty < 0 || 1 / Qty < 0; // OK for +-0
            if (!isFinite(Qty)) {
                Bin = Inf[NumW];
                if (Sign)
                    Bin.d += 1 << (NumW / 4 - 1);
                Expo = Math.pow(2, ExW) - 1;
                Mant = 0;
            }
        }

        if (!Bin) {
            Expo = {32: 127, 64: 1023}[NumW];
            Mant = Math.abs(Qty);
            while (Mant >= 2) {
                Expo++;
                Mant /= 2;
            }
            while (Mant < 1 && Expo > 0) {
                Expo--;
                Mant *= 2;
            }
            if (Expo <= 0) {
                Mant /= 2;
                nb01 = "Zero or Denormal";
            }
            if (NumW == 32 && Expo > 254) {
                nb01 = "Too big for Single";
                Bin = {
                    d : Sign ? 0xFF : 0x7F,
                    c : 0x80,
                    b : 0,
                    a : 0
                };
                Expo = Math.pow(2, ExW) - 1;
                Mant = 0;
            }
        }

        if (!Bin)
            Bin = {32: SngFwd, 64: DblFwd}[NumW](Sign, Expo, Mant);

        Bin.sgn = +Sign;
        Bin.exp = numberToBinString(Expo, ExW);
        Mant = (Mant % 1) * Math.pow(2, MtW);
        if (NumW == 32)
            Mant = Math.floor(Mant + 0.5);
        Bin.mnt = numberToBinString(Mant, MtW);
        Bin.nb01 = nb01;
        return Bin;
    }

    function CVTREV(BinStr) {
        var ExW = {32: 8,64: 11}[BinStr.length];
        var M = BinStr.match(new RegExp("^(.)(.{" + ExW + "})(.*)$"));
        // M1 sign, M2 exponent, M3 mantissa

        var Sign = M[1] == "1" ? -1 : +1;

        if (!/0/.test(M[2])) { // NaN or Inf
            var X = /1/.test(M[3]) ? NaN : Sign / 0;
            throw new Error("Max Coded " + M[3] + " " + X.toString());
        }

        var Denorm = +M[2] == 0;
        if (Denorm) {
            console.log("Zero or Denormal");
        }

        var Expo = parseInt(M[2], 2) - Math.pow(2, ExW - 1) + 1;
        var Mant = parseInt(M[3], 2) / Math.pow(2, M[3].length) + !Denorm;
        return Sign * Mant * Math.pow(2, Expo + Denorm);
    }

    this.doubleToHexString = function( /* double */d, /* int */size) {
        var NumW = size;
        var Qty = d;
        with (CVTFWD(NumW, Qty)) {
            return binStringToHexString(sgn + exp + mnt);
        }
    };

    this.hexStringToDouble = function (/*String*/hexString, /*int*/size) {
        var NumW = size ;
        var binString = hexStringToBinString(hexString) ;
        var X = new RegExp("^[01]{" + NumW + "}$");
        if (!X.test(binString)) {
            alert(NumW + " bits 0/1 needed");
            return;
        }
        return CVTREV(binString);
    };
};
Younes
  • 1,635
  • 1
  • 19
  • 33