提取 Javascript 数字的指数和尾数

问题描述 投票:0回答:8

有没有一种相当快速的方法来从 Javascript 中的数字中提取指数和尾数?

AFAIK 没有办法获取 Javascript 中 Number 后面的位,这让我觉得我正在研究因式分解问题:找到

m
n
,使得
2^n * m = k
对于给定的
k 
。由于整数分解属于 NP 范畴,我只能假设这将是一个相当困难的问题。

我正在实现一个用于生成 Javascript 的 GHC 插件,并且需要实现

decodeFloat_Int#
decodeDouble_2Int#
原始操作;我想我可以重写基础库的部分,这些部分使用该操作以其他方式执行任何操作(这应该不会太难,因为所有数字类型都以 Number 作为其表示形式,)但是它'如果我不需要的话就好了。

有没有什么方法可以通过一些黑暗的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};
}
javascript math haskell ghc ieee-754
8个回答
32
投票

使用新的

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");

6
投票

ECMAScript 没有定义任何直接的方法来执行此操作;但就其价值而言,这并不是与质因数分解相同意义上的“因式分解问题”。

理论上,您可以通过首先处理符号,然后使用二叉树方法(或对数)来查找指数,最后除以 2 的相关幂以获得尾数,从而非常快速地完成您想要的操作;但不幸的是,在实践中实现这一点可能有点棘手(对于特殊情况,例如非规范化数字)。我建议您仔细阅读 ECMAScript 规范的第 6.1.6.1 节:数字类型,以了解您必须处理哪些情况。


6
投票

为此根本不需要整数分解。

指数基本上就是以 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 支持这些操作。


4
投票

对于以 10 为基数的情况,您可以使用以下方法获取数组中的尾数和指数:

var myarray = (number.toExponential() + '').split("e"); // then ... var mantissa = parseFloat(myarray[0]); var exponent = parseInt(myarray[1]);

如果您不关心结果部分是否是文本而不是数字,并且指数部分前面可能有加号,那么您可以跳过

parseFloat

parseInt
 步骤,只获取部分直接来自 [0] 和 [1] 处的数组。


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 }
    

3
投票
下面获取指数怎么样?:

let exp = String(number.toExponential()); exp = Number(exp.substr(exp.lastIndexOf('e')+1));

1000 将导致 exp = 3


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; }


-1
投票
如果您只需要尾数长度,

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();
    
© www.soinside.com 2019 - 2024. All rights reserved.