Подтвердить что ты не робот

Извлечение экспоненты и мантиссы Javascript Number

Существует ли достаточно быстрый способ извлечения экспоненты и мантиссы из числа в Javascript?

AFAIK нет способа получить бит за номером в Javascript, что заставляет меня казаться, что я смотрю на проблему факторизации: найти m и n, чтобы 2^n * m = k для данного k. Поскольку целочисленная факторизация в NP, я могу только предположить, что это будет довольно трудной проблемой.

Я внедряю плагин GHC для генерации Javascript и должен выполнить примитивные операции decodeFloat_Int# и decodeDouble_2Int# ; Я думаю, я мог бы просто переписать части базовой библиотеки, которая использует операцию, чтобы сделать wahtever, они делают другим способом (что не должно быть слишком сложным, поскольку все числовые типы имеют Number как их представление в любом случае), но это означает, d быть хорошо, если бы мне не пришлось.

Есть ли способ сделать это хотя бы удаленно, с помощью какого-то темного Javascript voodoo, умной математики или каких-то других средств, или я должен просто спрятать и иметь в базовой библиотеке?

ИЗМЕНИТЬ Основываясь на замечательных ответах Руаха и Луи Вассермана, я придумал следующую реализацию, которая, кажется, работает достаточно хорошо:

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

Ответ 1

ECMAScript не определяет простой способ сделать это; но для чего это стоит, это не "проблема факторизации" в том же смысле, что и простая факторизация.

То, что вы хотите, теоретически может быть сделано очень быстро, сначала обработав знак, а затем используя двоичный древовидный подход (или логарифм), чтобы найти экспонента, и, наконец, разделив на соответствующую силу два, чтобы получить мантиссу; но, к сожалению, может быть несколько сложно реализовать это на практике (что в особых случаях, таких как денормализованные числа). Я рекомендую вам прочитать раздел 8.5 спецификации ECMAScript, чтобы понять, в каких случаях вам придется обращаться.

Ответ 2

Используя новые ArrayBuffer доступа ArrayBuffer, на самом деле можно получить точные мантиссу и экспоненту, извлекая их из Uint8Array. Если вам нужна большая скорость, Float64Array повторно использовать 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");

Ответ 3

Целочисленная факторизация нигде не нужна для этого.

Показателем в основном будет пол логарифма base-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

В то время как мне понравилось принятое решение, используя его для работы на произвольной основе, повторно введены все ошибки, вызванные 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 и Бесконечные случаи могут быть легко добавлены. Если важно различие между +0 и -0:

if (1/x === Infinity) return { sign: 0, mantissa: 0, exponent: 0 }
if (1/x === -Infinity) return { sign: 1, mantissa: 0, exponent: 0 }

Ответ 5

Мой 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;
}

Ответ 6

Как насчет следующего, чтобы получить показатель экспоненты?:

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

1000 приведет к exp = 3

Ответ 7

Если вам нужна только длина мантиссы,

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

Для базы 10 вы можете получить мантиссу и экспоненту в массиве с

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

Если вас не волнует, являются ли результирующие части текстовыми, а не числовыми, и если в передней части экспоненты может присутствовать знак плюс, вы можете пропустить шаги parseFloat и parseInt и просто взять части непосредственно из массива в [0] и [1].