haskell 提取JavaScript Number的指数和尾数

cnjp1d6j  于 9个月前  发布在  Java
关注(0)|答案(8)|浏览(103)

有没有一个合理的快速方法来提取指数和尾数从一个数字在JavaScript?
在JavaScript中,没有办法得到数字后面的位,这让我觉得我在看一个因式分解问题:找到mn,使得对于给定的k2^n * m = k。由于整数因式分解是NP,我只能假设这是一个相当困难的问题。
我正在实现一个用于生成JavaScript的GHC插件,并且需要实现decodeFloat_Int#decodeDouble_2Int#primitive operations;我想我可以重写基础库的部分,这些部分使用操作来做其他方式(这应该不会太难,因为所有数字类型都有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};
}

字符串

r6l8ljro

r6l8ljro1#

使用新的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");

prdp8dxp

prdp8dxp2#

ECMAScript没有定义任何直接的方法来实现这一点;但无论如何,这不是一个与素因子分解相同意义上的“因子分解问题”。
理论上,你可以很快地完成你想要的,首先处理符号,然后使用二叉树方法(或对数)来找到指数,最后除以2的相关幂来得到尾数;但不幸的是,在实践中实现这一点可能有点棘手(对于特殊情况,如非正规化的数字)。我建议你阅读6.1.6.1:ECMAScript规范的数字类型一节,了解你必须处理什么情况。

d5vmydt9

d5vmydt93#

整数因式分解对于这一点来说几乎是不必要的。
这个指数基本上就是以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支持这些操作。

irlmq6kh

irlmq6kh4#

对于基数为10的数组,你可以用

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

字符串
如果你不在乎结果部分是文本而不是数字,或者指数部分的前面可能有一个加号,那么你可以跳过parseFloatparseInt步骤,直接从数组的[0]和[1]处获取部分。

vzgqcmou

vzgqcmou5#

虽然我喜欢这个公认的解决方案,但使用它来处理任意碱基会重新引入Math.logMath.pow引起的所有错误。

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 }

bq9c1y66

bq9c1y666#

下面我们来看看如何得到指数:

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

字符串
1000将导致exp = 3

z9zf31ra

z9zf31ra7#

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

字符串

f1tvaqid

f1tvaqid8#

如果你只需要尾数长度,

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

字符串

相关问题