是否有一种相当快速的方法从Javascript中的数字中提取指数和尾数?
AFAIK没有办法在Javascript中找到一个数字后面的位,这让我觉得我正在考虑一个因子分解问题:找m
和n
这样2^n * m = k
1}}对于给定的k
。由于整数分解是在NP中,我只能假设这将是一个相当困难的问题。
我正在实施一个用于生成Javascript的GHC插件,需要实现decodeFloat_Int#
和decodeDouble_2Int#
primitive operations;我想我可以重写基本库的部分,这些部分使用操作来执行其他方式的操作(这应该不会太难,因为所有数字类型都将数字作为它们的表示,但是它'如果我不需要,那就好了。
有没有办法以一种非常高效的方式,通过一些黑暗的Javascript伏都教,聪明的数学或其他方法来做到这一点,或者我应该只是扣下来并拥有基础库?
修改 基于ruakh和Louis Wasserman的出色答案,我想出了以下实现,这似乎运作得很好:
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};
}
答案 0 :(得分:20)
使用新的ArrayBuffer
访问数组,实际上可以通过从Uint8Array
中提取它们来检索精确的尾数和指数。如果您需要更快的速度,请考虑重复使用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],
}
}
我还创建了一些测试用例。 0
失败,因为还有另一个表示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");
答案 1 :(得分:6)
ECMAScript没有定义任何直接的方法来做到这一点;但是对于它的价值而言,这不是与因素分解相同意义上的“分解问题”。
理论上可以通过首先处理符号,然后使用二叉树方法(或对数)来找到指数,然后除以2的相关幂来得到尾数,从而可以非常快速地完成所需的操作;但不幸的是,在实践中实现这一点可能有些棘手(对于非规范化数字等特殊情况)。我建议您阅读ECMAScript规范的第8.5节,以了解您将需要处理的案例。
答案 2 :(得分:5)
整数因子分解对此至关重要。
指数基本上是基数为2的对数的底限,这并不难计算。
以下代码通过了QuickCheck测试,以及无穷大和负无穷大的测试:
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)
我使用quickCheck (\ d -> decodeFloat d == decode d)
对其进行了测试,并在正面和负面无限下单独进行了测试。
这里使用的唯一原始操作是左移,双乘,双除,无穷大和NaN测试,Javascript支持我所知。
答案 3 :(得分:3)
虽然我喜欢已接受的解决方案,但使用它来处理任意基础会重新引入由Math.log
和Math.pow
引起的所有错误。以下是任何基础的小型实现: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 }
}
可以轻松添加NaN和Infinite案例。如果+0
和-0
之间的区别很重要:
if (1/x === Infinity) return { sign: 0, mantissa: 0, exponent: 0 }
if (1/x === -Infinity) return { sign: 1, mantissa: 0, exponent: 0 }
答案 4 :(得分:1)
我的Haskell不存在。这是JavaScript的解决方案。正如其他人所指出的那样,关键是计算二进制对数以得到指数。
来自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;
}
答案 5 :(得分:1)
如何获得指数?:
let exp = String(number.toExponential());
exp = Number(exp.substr(exp.lastIndexOf('e')+1));
1000将导致exp = 3
答案 6 :(得分:0)
如果你只需要尾数长度,
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();
答案 7 :(得分:0)
对于以10为底的基数,您可以使用数组中的尾数和指数
var myarray = (number.toExponential() + '').split("e");
// then ...
var mantissa = parseFloat(myarray[0]);
var exponent = parseInt(myarray[1]);
如果您不在乎结果部分是文本而不是数字,并且指数部分的前面可能有加号,则可以跳过parseFloat
和parseInt
步骤,然后直接从[0]和[1]处的数组中取出零件。