【问题标题】:Extracting the exponent and mantissa of a Javascript Number提取 Javascript 数字的指数和尾数
【发布时间】:2012-03-12 02:52:12
【问题描述】:

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

AFAIK 无法获取 Javascript 中数字后面的位,这让我觉得我正在研究一个因式分解问题:找到 mn 使得 2^n * m = k 对于给定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};
}

【问题讨论】:

  • 您的解决方案对我来说似乎有风险。特别是,我非常怀疑Math.log(x) * Math.LOG2E 中的小舍入错误会给你类似5.99999996floor 将其截断为5。继续自己做对数;它不太容易出现舍入错误。
  • @LouisWasserman:这是一个很好的观点;我最好这样做。谢谢!

标签: javascript math haskell ghc ieee-754


【解决方案1】:

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

【讨论】:

  • 此解决方案可在不支持非规范化数字的 iPad 上运行
  • 这会为我返回一个浮点尾数。返回整数尾数不是重点吗?
  • 如何在没有整数的语言中返回整数?使用这种相同的方法,您可以实现自己的数字类型顺便说一句:D
  • @JonathanWilbur 这个算法不会给你比特。它为您提供了它们所代表的内容。尾数定义为 1 到 2 之间的数字。这样尾数 = 1,b51 b50... b0 以 2 为底。en.wikipedia.org/wiki/Double-precision_floating-point_format
【解决方案2】:

ECMAScript 没有定义任何直接的方法来做到这一点。但就其价值而言,这不是与素数分解相同意义上的“分解问题”。

你想要的理论上可以很快完成,首先处理符号,然后使用二叉树方法(或对数)找到指数,最后除以相关的 2 的幂得到尾数;但不幸的是,在实践中实现这一点可能有些棘手(对于非规范化数字等特殊情况)。我建议您通读 ECMAScript 规范的第 8.5 节,以了解您必须处理哪些情况。

【讨论】:

  • 因式分解在 NP 中毫无疑问是,它只是不被认为是 NP 难的。你把它们弄混了。
  • @LouisWasserman:嗯,我在网上找到了相互矛盾的信息。我将删除我的答案的那部分,因为它并不重要。谢谢。
  • 谢谢;但是,这种方法(至少如果使用以 2 为底的对数来获得指数)不会为大多数数字提供非整数尾数吗?
  • @valderman:这是个好问题。我习惯于将尾数视为 [1.0, 2.0) 范围内的值,将指数视为 [-1022, +1023] 范围内的整数,但 EMCAScript 以 [ 2^52, 2^53) 和 [-1074, +971] 范围内的整数(因此避免使用术语“尾数”和“指数”),Haskell 似乎也这样做(尽管它确实使用了这些术语)。即便如此,通过在执行对数后加入 -52 的偏移量,这很容易解决。
  • @ruakh:简单地说,NP 是一类问题,其解决方案可以在多项式时间内检查。乘法需要多项式时间,因此因式分解在 NP 中。
【解决方案3】:

整数分解几乎没有必要。

指数基本上是以 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 -&gt; decodeFloat d == decode d) 对其进行了测试,并在正无穷和负无穷上分别明确地对其进行了测试。

这里使用的唯一原始操作是左移、双乘、双除以及无穷大和 NaN 测试,据我所知,Javascript 支持这些操作。

【讨论】:

  • 是的。如果 Haskell 的 decodeFloat 操作与我预期的相差不远,我会早点完成的!不过,我最近在 Java 中做了 很多 的 IEEE754 黑客操作,这很有帮助。
  • 你的minNormalizedDouble实际上是非规范化的,最小的正规范化Double0.5 ^ 1022。由于a ^^ b = 1/(a ^ (-b))b &lt; 0 的影响,0 &lt; d &lt; minNormalizedDouble 的指数溢出,给出了无意义的结果(你正在四舍五入Infinity)。 let e = exponentOf d - 53; twoE | e &lt; 0 = 0.5 ^ (-e) | otherwise = 2 ^ e 解决了这个问题。 decodeFloat 与您的预期相差多少?
  • 已修复。我想我觉得我希望Int64 参与其中,而不是Integer;特别是,我想要一种直接在Int64 中获取双精度位的方法。
【解决方案4】:

虽然我喜欢接受的解决方案,但使用它在任意基础上工作重新引入了由 Math.logMath.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 }

【讨论】:

  • 天哪,终于找到了。我已经在 10 天前看到了你的答案,但还是使用了 log/pow。然后我开始遇到四舍五入的问题并去寻找你的答案。我搜索了我能记住的每个相关关键字,阅读了大量类似问题的所有答案,然后花了半分钟浏览了我的整个浏览历史记录。阅读 8 行代码从未如此高兴。谢谢你:)
  • 请注意,如果x为零,则会出错!您应该使用 if 来捕捉它。
  • @user1703497 谢谢,对常见的0 情况和不太常见的-0+0 情况进行了一些编辑
  • ...又找到了。该死的,我这辈子需要多少次?
  • 截至 2021 年 7 月 28 日,这是所有解决方案中最快的一个。比较:jsbench.me/hokro6nel4/1
【解决方案5】:

对于以 10 为底的数,您可以使用数组获取尾数和指数

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

如果您不关心结果部分是否是文本而不是数字,并且指数部分前面是否有加号,那么您可以跳过 parseFloatparseInt 步骤,只需执行直接从数组 [0] 和 [1] 中提取的部分。

【讨论】:

    【解决方案6】:

    接下来如何获得指数?:

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

    1000 将导致 exp = 3

    【讨论】:

    • 如果您想要以 10 为底的指数,那么为什么不这样做呢? var exponent = Math.log(number) * Math.LOG10E;
    • 显然现在有一个 Math.log10() - 也可以与 Math.floor() 结合
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      如果只需要尾数长度,

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

      【讨论】:

        猜你喜欢
        • 2010-09-28
        • 1970-01-01
        • 2015-07-20
        • 2013-03-19
        • 2019-07-27
        • 1970-01-01
        • 2020-12-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多