Is there a reasonably fast way to extract the exponent and mantissa from a Number in Javascript?
AFAIK there's no way to get at the bits behind a Number in Javascript, which makes it seem to me that I'm looking at a factorization problem: finding m
and n
such that 2^n * m = k
for a given k
. Since integer factorization is in NP, I can only assume that this would be a fairly hard problem.
I'm implementing a GHC plugin for generating Javascript and need to implement the decodeFloat_Int#
and decodeDouble_2Int#
primitive operations; I guess I could just rewrite the parts of the base library that uses the operation to do wahtever they're doing in some other way (which shouldn't be too hard since all numeric types have Number as their representation anyway,) but it'd be nice if I didn't have to.
Is there any way to do this in an even remotely performant way, by some dark Javascript voodoo, clever mathematics or some other means, or should I just buckle down and have at the base library?
EDIT Based on ruakh's and Louis Wasserman's excellent answers, I came up with the following implementation, which seems to work well enough:
function getNumberParts(x) {
if(isNaN(x)) {
return {mantissa: -6755399441055744, exponent: 972};
}
var sig = x > 0 ? 1 : -1;
if(!isFinite(x)) {
return {mantissa: sig * 4503599627370496, exponent: 972};
}
x = Math.abs(x);
var exp = Math.floor(Math.log(x)*Math.LOG2E)-52;
var man = x/Math.pow(2, exp);
return {mantissa: sig*man, exponent: exp};
}
Using the new ArrayBuffer
access arrays, it is actually possible to retrieve the exact mantissa and exponent, by extracting them from the Uint8Array
. If you need more speed, consider reusing the Float64Array
.
function getNumberParts(x)
{
var float = new Float64Array(1),
bytes = new Uint8Array(float.buffer);
float[0] = x;
var sign = bytes[7] >> 7,
exponent = ((bytes[7] & 0x7f) << 4 | bytes[6] >> 4) - 0x3ff;
bytes[7] = 0x3f;
bytes[6] |= 0xf0;
return {
sign: sign,
exponent: exponent,
mantissa: float[0],
}
}
I've also created some test cases. 0
fails, since there is another representation for 2^-1023.
var tests = [1, -1, .123, -.123, 1.5, -1.5, 1e100, -1e100,
1e-100, -1e-100, Infinity, -Infinity];
tests.forEach(function(x)
{
var parts = getNumberParts(x),
value = Math.pow(-1, parts.sign) *
Math.pow(2, parts.exponent) *
parts.mantissa;
console.log("Testing: " + x + " " + value);
console.assert(x === value);
});
console.log("Tests passed");