getting the exponent and mantissa from a JavaScript number

June 21, 2012 — 2 Comments

… or what to do when there’s no DataView.

updated: corrected the output byte order in formatIEEE64().

i recently needed to be able to serialize a JavaScript Number to a 64-bit IEEE 754 floating point format.

there were two main challenges to overcome:

  1. there is no universal way to get direct access to the bits that make up the JavaScript Number. DataView will change this, but it’s not available on IE9. So I needed to find a way to decode the JavaScript number into its constituent sign, exponent, and mantissa, with proper handling of denormalized values.
  2. the mantissa, once calculated, is a 52-bit value, so we can’t use JavaScript’s bitwise operators to access the bits of the mantissa. The bitwise operators all coerce their arguments to 32-bit values.

jDataView is a library that has the ability to read 64-bit IEEE values from a buffer, and it has been recently updated to also support writing 64-bit IEEE values.

here is my solution to the problem until DataView becomes widely available.

/*
 * Getting the exponent and mantissa from a JavaScript number
 * By Monroe Thomas http://blog.coolmuse.com
 *
 * MIT Licensed.
 *
 */

function decodeIEEE64 ( value ) {

  if ( typeof value !== "number" )
    throw new TypeError( "value must be a Number" );

  var result = {
    isNegative : false,
    exponent : 0,
    mantissa : 0
  };

  if ( value === 0 ) {
    return result;
  }

  // not finite?
  if ( !isFinite( value ) ) {
    result.exponent = 2047;
    if ( isNaN( value ) ) {
      result.isNegative = false;
      result.mantissa = 2251799813685248; // QNan
    } else {
      result.isNegative = value === -Infinity;
      result.mantissa = 0;
    }
    return result;
  }

  // negative?
  if ( value < 0 ) {  result.isNegative = true;  value = -value;  }

  // calculate biased exponent
  var e = 0;
  if ( value >= Math.pow( 2, -1022 ) ) { // not denormalized
    // calculate integer part of binary logarithm
    // http://en.wikipedia.org/wiki/Binary_logarithm
    var r = value;
    while ( r < 1 ) { e -= 1; r *= 2; }
    while ( r >= 2 ) { e += 1; r /= 2; }
    e += 1023; // add bias
  }
  result.exponent = e;

  // calculate mantissa
  if ( e != 0 ) {
    var f = value / Math.pow( 2, e - 1023 );
    result.mantissa = Math.floor( (f - 1) * Math.pow( 2, 52 ) );
  } else { // denormalized
    result.mantissa = Math.floor( value / Math.pow( 2, -1074 ) );
  }

  return result;
}

function formatIEEE64(value, isBigEndian) {

  var ieee64 = decodeIEEE64(value);
  isBigEndian = !!isBigEndian;

  var e = ieee64.exponent;
  var m = ieee64.mantissa;

  var b7 = (ieee64.isNegative ? 0x80 : 0x00) | (e >> 4);
  var b6 = ((e & 0x0F) << 4) | (m / 281474976710656); // 2^48
  var b5 = 0 | ((m % 281474976710656) / 1099511627776); // 2^48, 2^40
  var b4 = 0 | ((m % 1099511627776) / 4294967296); // 2^40, 2^32
  var b3 = 0 | ((m % 4294967296) / 16777216); // 2^32, 2^24
  var b2 = 0 | ((m % 16777216) / 65536); // 2^24, 2^16  
  var b1 = 0 | ((m % 65536) / 256); // 2^16, 2^8
  var b0 = 0 | (m % 256); // 2^8

  if (isBigEndian) return [ b7, b6, b5, b4, b3, b2, b1, b0 ];
  return [ b0, b1, b2, b3, b4, b5, b6, b7 ];
}

var test1 = formatIEEE64(0, true); // -> 0000000000000000
var test2 = formatIEEE64(NaN, true); // -> FFF8000000000000
var test3 = formatIEEE64(-Infinity, true); // -> FFF0000000000000
var test4 = formatIEEE64(Infinity, true); // -> 7FF0000000000000
var test5 = formatIEEE64(Math.pow(2, -1074), true); // -> 0000000000000001
var test6 = formatIEEE64(-Math.pow(2, -1074), true); // -> 8000000000000001
var test7 = formatIEEE64(Math.pow(2, -1022), true); // -> 0010000000000000
var test8 = formatIEEE64(-Math.pow(2, -1022), true); // -> 8010000000000000
var test9 = formatIEEE64(Math.pow(2, 1023), true); // -> 7FE0000000000000
var test10 = formatIEEE64(-Math.pow(2, 1023), true); // -> FFE0000000000000
var test11 = formatIEEE64(0.1, true); // -> 3FB999999999999A
var test12 = formatIEEE64(-0.1, true); // -> BFB999999999999A
var test13 = formatIEEE64(10e100, true); // -> 54E6DC186EF9F45C
var test14 = formatIEEE64(-10e100, true); // -> D4E6DC186EF9F45C

ciao!

2 responses to getting the exponent and mantissa from a JavaScript number

  1. 

    I don’t understand.

    var decoded = decodeIEEE64(1024);
    // -> { isNegative: false, exponent: 1033, mantissa: 0 }

    …why?

    • 

      Yanick,

      In IEEE64, the exponent for normalized floating point values are offset by 1023, and the mantissa is a binary fraction that is added to 1.

      So, when you are representing a value that is a power of two, such as 1024, the mantissa will always be zero, and the exponent will be 1023 + log2(value).

      As another example:

      var decoded = decodeIEEE64(2048.0);
      // -> { isNegative: false, exponent: 1034, mantissa: 0 }

      2048.0 == 1.0 x 2^11, therefore
      exponent = 1023 + 11 == 1034
      mantissa = 1.0 – 1 == 0

      See http://en.wikipedia.org/wiki/IEEE_754-1985 for more information on the representation of real numbers using floating point conventions.

      Hope this helps.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s