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.
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>
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);
};
};
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>