问题
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};
}
回答1:
ECMAScript doesn't define any straightforward way to do this; but for what it's worth, this isn't a "factorization problem" in the same sense as prime factorization.
What you want can theoretically be done very quickly by first handling the sign, then using a binary-tree approach (or logarithm) to find the exponent, and lastly dividing by the relevant power of two to get the mantissa; but unfortunately, it can be somewhat tricky to implement this in practice (what with special cases such as denormalized numbers). I recommend you read through section 8.5 of the ECMAScript specification to get a sense of what cases you'll have to handle.
回答2:
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");
回答3:
Integer factorization is nowhere near necessary for this.
The exponent is basically going to be the floor of the base-2 logarithm, which isn't that hard to compute.
The following code passes QuickCheck tests, as well as tests on infinity and negative infinity:
minNormalizedDouble :: Double
minNormalizedDouble = 2 ^^ (-1022)
powers :: [(Int, Double)]
powers = [(b, 2.0 ^^ fromIntegral b) | i <- [9, 8..0], let b = bit i]
exponentOf :: Double -> Int
exponentOf d
| d < 0 = exponentOf (-d)
| d < minNormalizedDouble = -1024
| d < 1 =
let go (dd, accum) (p, twoP)
| dd * twoP < 1 = (dd * twoP, accum - p)
| otherwise = (dd, accum)
in snd $ foldl' go (d, 0) powers
| otherwise =
let go (x, accum) (p, twoP)
| x * twoP <= d = (x * twoP, accum + p)
| otherwise = (x, accum)
in 1 + (snd $ foldl' go (1.0, 0) powers)
decode :: Double -> (Integer, Int)
decode 0.0 = (0, 0)
decode d
| isInfinite d, d > 0 = (4503599627370496, 972)
| isInfinite d, d < 0 = (-4503599627370496, 972)
| isNaN d = (-6755399441055744, 972)
| otherwise =
let
e = exponentOf d - 53
twoE = 2.0 ^^ e
in (round (d / twoE), e)
I tested it using quickCheck (\ d -> decodeFloat d == decode d)
, and explicitly tested it separately on positive and negative infinities.
The only primitive operations used here are left-shift, double multiplication, double division, and infinity and NaN testing, which Javascript supports to the best of my knowledge.
回答4:
While I liked the accepted solution, using it to work on arbitrary base re-introduced all of the errors caused by Math.log
and Math.pow
. Here is a small implementation for any base: x = mantisse * b^exponent
function numberParts(x, b) {
var exp = 0
var sgn = 0
if (x === 0) return { sign: 0, mantissa: 0, exponent: 0 }
if (x<0) sgn=1, x=-x
while (x>b) x/=b, exp++
while (x<1) x*=b, exp--
return { sign: sgn, mantissa: x, exponent: exp }
}
The NaN and Infinite cases can easily be added. If the distinction between +0
and -0
is important:
if (1/x === Infinity) return { sign: 0, mantissa: 0, exponent: 0 }
if (1/x === -Infinity) return { sign: 1, mantissa: 0, exponent: 0 }
回答5:
My Haskell is non-existent. Here's a solution in JavaScript. As others have noted, the key is to calculate the binary logarithm to get the exponent.
From http://blog.coolmuse.com/2012/06/21/getting-the-exponent-and-mantissa-from-a-javascript-number/
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
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;
}
回答6:
What about following to get the exponent?:
let exp = String(number.toExponential());
exp = Number(exp.substr(exp.lastIndexOf('e')+1));
1000 will result in exp = 3
回答7:
If you only need mantissa length,
Number.prototype.mantissaLength = function(){
var m = this.toString(), d = m.indexOf('.') + 1;
return d? m.length - d:0;
}
var x = 1234.5678;
var mantL = x.mantissaLength();
回答8:
For base 10 you can get mantissa and exponent in an array with
var myarray = (number.toExponential() + '').split("e");
// then ...
var mantissa = parseFloat(myarray[0]);
var exponent = parseInt(myarray[1]);
If you don't care if the result parts are text instead of numbers and if there might be a plus sign on the front of the exponent part, then you can skip the parseFloat
and parseInt
steps and just take the parts directly from the array at [0] and [1].
来源:https://stackoverflow.com/questions/9383593/extracting-the-exponent-and-mantissa-of-a-javascript-number