Search code examples
javascriptnumbersbitmask

Shifting an integer in JavaScript for storage?


I've run into an issue that seems a bit weird and am wondering if anyone might be able to help with it.

I'm using JavaScript to store some integers via node.js and would like to use the full range of integers from Number.MIN_SAFE_INTEGER through Number.MAX_SAFE_INTEGER just calling negative zero a zero.

From w3schools it appears there are only 52 bits allowed (the first 52 bits), ignoring the sign, to retrieve Number.MAX_SAFE_INTEGER however it is clearly equal to (2 ^ 53) - 1.

The ECMA spec on the otherhand says there are (2 ^ 53) - 2 values (appearing to use -0 as NaN.

I'm trying to pack integers into the smallest bitspace possible, 1-bit (1) sign, 1-bit (2) for null and the remaining bits for successively larger numbers. This works great just adding more lines up until the last line of:

// sign is 0 for positive, 1 for negative and 2 for null
byte[0] = ((temp << 2) & 255) + sign;
byte[1] = (temp >> 6) & 255;
byte[2] = (temp >> 14) & 255;
byte[3] = (temp >> 22) & 255;
byte[4] = (temp >> 30) & 255;
byte[5] = (temp >> 38) & 255;
byte[6] = (temp >> 46) & 255; // produces a negative value prior to applying byte mask

Here's a fiddle with some relevant code incase it helps.


Solution

  • The IEEE floating point format has an implied leading 1 bit on the mantissa, but it's not actually present. In other words, all valid values have a leading 1 bit, so there's no point actually having it be explicitly stored.

    You can look at this jsfiddle I did the other day to see how values are represented. Integers are represented as binary fractions multiplied by 2 raised to a power larger than 1, except of course for the integer 1, which has a mantissa of all zeros because of that leading implied 1 bit. (Actually every power of 2 is all-zeros in the mantissa.)

    edit — for reference, the most sure-fire way I know to get directly at the bits in a floating-point value is to use typed arrays:

    var f64 = new Float64Array(1);
    var u8 = new Uint8Array(f64.buffer);
    

    That gives you a one-element 64-bit array, and an 8 element unsigned 8-bit int array. Put a number in the f64 array:

    f64[0] = Math.sin(x); // or whatever
    

    Then the u8 array gives you the 8 bytes of that value, with the most significant byte (where the sign bit and exponent are) at u8[7]. IE versions less than 10 don't support typed arrays, nor does Opera Mini.