4

I'm trying to convert a large number into an 8 byte array in javascript.

Here is an IMEI that I am passing in: 45035997012373300

var bytes = new Array(7);
for(var k=0;k<8;k++) {
  bytes[k] = value & (255);
  value = value / 256;
}

This ends up giving the byte array: 48,47,7,44,0,0,160,0. Converted back to a long, the value is 45035997012373296, which is 4 less than the correct value.

Any idea why this is and how I can fix it to serialize into the correct bytes?

Phrogz
  • 296,393
  • 112
  • 651
  • 745
Justin
  • 17,670
  • 38
  • 132
  • 201

3 Answers3

3

Since you are converting from decimal to bytes, dividing by 256 is an operation that is pretty easily simulated by splitting up a number in a string into parts. There are two mathematical rules that we can take advantage of.

  1. The right-most n digits of a decimal number can determine divisibility by 2^n.
  2. 10^n will always be divisible by 2^n.

Thus we can take the number and split off the right-most 8 digits to find the remainder (i.e., & 255), divide the right part by 256, and then also divide the left part of the number by 256 separately. The remainder from the left part can be shifted into the right part of the number (the right-most 8 digits) by the formula n*10^8 \ 256 = (q*256+r)*10^8 \ 256 = q*256*10^8\256 + r*10^8\256 = q*10^8 + r*5^8, where \ is integer division and q and r are quotient and remainder, respectively for n \ 256. This yields the following method to do integer division by 256 for strings of up to 23 digits (15 normal JS precision + 8 extra yielded by this method) in length:

function divide256(n)
{
    if (n.length <= 8)
    {
        return (Math.floor(parseInt(n) / 256)).toString();
    }
    else
    {
        var top = n.substring(0, n.length - 8);
        var bottom = n.substring(n.length - 8);
        var topVal = Math.floor(parseInt(top) / 256);
        var bottomVal = Math.floor(parseInt(bottom) / 256);
        var rem = (100000000 / 256) * (parseInt(top) % 256);
        bottomVal += rem;
        topVal += Math.floor(bottomVal / 100000000); // shift back possible carry
        bottomVal %= 100000000;
        if (topVal == 0) return bottomVal.toString();
        else return topVal.toString() + bottomVal.toString();
    }
}

Technically this could be implemented to divide an integer of any arbitrary size by 256, simply by recursively breaking the number into 8-digit parts and handling the division of each part separately using the same method.

Here is a working implementation that calculates the correct byte array for your example number (45035997012373300): http://jsfiddle.net/kkX2U/.

[52, 47, 7, 44, 0, 0, 160, 0]
mellamokb
  • 56,094
  • 12
  • 110
  • 136
  • Your array is in reverse order (showing bytes from least-significant to most). – Phrogz Apr 20 '12 at 22:12
  • It's in the same order posted by the OP, and is also the correct order for `BitConverter.ToInt64` in `C#` as I've tested it. – mellamokb Apr 20 '12 at 22:15
  • But it differs from my answer, so it must be wrong. :) I didn't notice the "reversed" order of bytes by the OP; good point. – Phrogz Apr 20 '12 at 22:18
  • Ya as I commented on OP you can test with this in LINQPad: `BitConverter.ToInt64(new byte[] {52,47,7,44,0,0,160,0}, 0)`. I assume conversion takes them in the same order sent, and not FIFO over the UDP stream. The networking/bit side of things I don't have a good grasp of. – mellamokb Apr 20 '12 at 22:20
2

Your value and the largest JavaScript integer compared:

45035997012373300  // Yours
 9007199254740992  // JavaScript's biggest integer

JavaScript cannot represent your original value exactly as an integer; that's why your script breaking it down gives you an inexact representation.

Related:

var diff = 45035997012373300 - 45035997012373298;
// 0 (not 2)

Edit: If you can express your number as a hexadecimal string:

function bytesFromHex(str,pad){
  if (str.length%2) str="0"+str;
  var bytes = str.match(/../g).map(function(s){
    return parseInt(s,16);
  });
  if (pad) for (var i=bytes.length;i<pad;++i) bytes.unshift(0);
  return bytes;
}

var imei = "a000002c072f34";
var bytes = bytesFromHex(imei,8);
// [0,160,0,0,44,7,47,52]

If you need the bytes ordered from least-to-most significant, throw a .reverse() on the result.

Community
  • 1
  • 1
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 1
    @mellamokb Try [changing the value to `45035997012373301`](http://jsfiddle.net/zuqgJ/1/). – Phrogz Apr 20 '12 at 21:18
  • @Phrogz - so what's the workaround? I can store the IMEI as a string in javascript to make sure I don't lose precision. But I still need a way to convert it to 8 bytes. – Justin Apr 20 '12 at 21:19
  • @Phrogz: Yep, looks like that's the problem. – mellamokb Apr 20 '12 at 21:19
  • where does the imei come from? i have implemented an entire bit manipulation library in javascript and i believe i can solve the problem, but i need to understand the source of the input – Ryan Apr 20 '12 at 21:28
  • See my edit if you can express your string as hex instead of decimal. Otherwise @mellamokb seems to have this under control. – Phrogz Apr 20 '12 at 22:13
  • Aren't you missing the eight bit, i.e., should have `0` at the beginning before `160`? – mellamokb Apr 20 '12 at 22:22
  • @mellamokb _(edits code quickly)_ I have no idea what you're talking about. ;) – Phrogz Apr 20 '12 at 22:25
  • @Phrogz: Too late.. Just missed the 5-minute window :P – mellamokb Apr 20 '12 at 22:27
  • Switched answer to this one. Thanks to both of you for your help, I originally was using melllamokb's code but it ended up not working for certain large values. I changed my code to return the IMEI in hex, and then serialize to bytes using Phrogz's code, and it works great! – Justin May 10 '12 at 18:12
0

store the imei as a hex string (if you can), then parse the string in that manner, this way you can keep the precision when you build the array. I will be back with a PoC when i get home on my regular computer, if this question has not been answered.

something like:

function parseHexString(str){
   for (var i=0, j=0; i<str.length; i+=2, j++){
      array[j] = parseInt("0x"+str.substr(i, 2));
   }
}

or close to that whatever...

Ryan
  • 2,755
  • 16
  • 30