… 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:
- 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.
- 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!
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.