Convert integers to binary strings

If you want to visualize the binary digits of a number in JavaScript, you need to decode the floating point format as you build your string.

The simplest, but least accurate method is to use the built-in Number.toString(2).

JavaScript

let n = 254;
let nStr = n.toString(2);
console.log(nStr);
// outputs "11111110"

There are two issues with that output.

  1. It doesn't pad the result correctly, giving the false impression that 254 is stored in memory using only 8 bits.
  2. Negative numbers won't be in two's-complement format. Instead, toString() converts the number's absolute value to binary, then prepends a '-'.

(1) is relatively simple to handle with String.padStart(). (2) can be solved with a quirk of the >>> operator:

let n = -255;
let nStr = (n >>> 0).toString(2);
console.log(nStr);
// outputs "11111111111111111111111100000001"

">>>" apparently forces a conversion to an unsigned integer, whose bits are treated as-is by toString().

That result is pretty decent, but it won't cover numbers closer to the Number.MAXSAFEINTEGER range. The output above was only 32 bits long, but JavaScript numbers actually offer 52 bits of precision.

To get an accurate representation of any integer in the range [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], you'll need to do more work to decode the floating point container format.

This SO answer has an excellent solution:

JavaScript

// IIFE to scope internal variables
var float64ToInt64Binary = (function () {
  // create union
  var flt64 = new Float64Array(1);
  var uint16 = new Uint16Array(flt64.buffer);
  // 2**53-1
  var MAX_SAFE = 9007199254740991;
  // 2**31
  var MAX_INT32 = 2147483648;

  function uint16ToBinary() {
    var bin64 = "";
    // generate padded binary string a word at a time
    for (var word = 0; word < 4; word++) {
      bin64 = uint16[word].toString(2).padStart(16, 0) + bin64;
    }
    return bin64;
  }
  return function float64ToInt64Binary(number) {
    // NaN would pass through Math.abs(number) > MAX_SAFE
    if (!(Math.abs(number) <= MAX_SAFE)) {
      throw new RangeError("Absolute value must be less than 2**53");
    }
    var sign = number < 0 ? 1 : 0;
    // shortcut using other answer for sufficiently small range
    if (Math.abs(number) <= MAX_INT32) {
      return (number >>> 0).toString(2).padStart(64, sign);
    }
    // little endian byte ordering
    flt64[0] = number;
    // subtract bias from exponent bits
    var exponent = ((uint16[3] & 0x7ff0) >> 4) - 1023;
    // encode implicit leading bit of mantissa
    uint16[3] |= 0x10;
    // clear exponent and sign bit
    uint16[3] &= 0x1f;
    // check sign bit
    if (sign === 1) {
      // apply two's complement
      uint16[0] ^= 0xffff;
      uint16[1] ^= 0xffff;
      uint16[2] ^= 0xffff;
      uint16[3] ^= 0xffff;
      // propagate carry bit
      for (var word = 0; word < 3 && uint16[word] === 0xffff; word++) {
        // apply integer overflow
        uint16[word] = 0;
      }
      // complete increment
      uint16[word]++;
    }
    // only keep integer part of mantissa
    var bin64 = uint16ToBinary().substr(11, Math.max(exponent, 0));
    // sign-extend binary string
    return bin64.padStart(64, sign);
  };
})();

Credit to Patrick Roberts for the solution.

[It] creates a union between a 64-bit floating point number and an unsigned 16-bit integer array [...] then uses the union to gain bit access to the value and calculate the binary string based on the unbiased binary exponent and fraction bits.

Using float64ToInt64Binary will give you a bitwise-correct integer in the full valid range. His solution relies heavily on knowledge of the floating point format. The Wikipedia entry is pretty dense, so I recommend "The Floating-Point Guide" if you want to learn more.

References:

  1. https://modernweb.com/what-every-javascript-developer-should-know-about-floating-points/
  2. http://floating-point-gui.de/formats/fp/
  3. http://fabiensanglard.net/floating_point_visually_explained/index.php

© 2021 David Mann. All content provided on this site, code or otherwise, is licensed and/or copyright as specified below: