Извлечение показателя степени и мантиссы числа Javascript - PullRequest
15 голосов
/ 21 февраля 2012

Есть ли достаточно быстрый способ извлечь показатель степени и мантиссу из числа в Javascript?

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

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

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

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

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

Ответы [ 8 ]

20 голосов
/ 18 июня 2013

Используя новые массивы доступа 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 голосов
/ 21 февраля 2012

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

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

5 голосов
/ 21 февраля 2012

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

Показатель степени в основном будет полем логарифма с базой 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 голосов
/ 07 июня 2015

В то время как мне понравилось принятое решение, использование его для работы на произвольной основе повторно ввело все ошибки, вызванные 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 }
1 голос
/ 25 февраля 2017

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

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

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

1 голос
/ 22 июня 2012

Мой Хаскель не существует.Вот решение в JavaScript.Как отметили другие, ключ заключается в том, чтобы вычислить двоичный логарифм для получения показателя степени.

От http://blog.coolmuse.com/2012/06/21/getting-the-exponent-and-mantissa-from-a-javascript-number/

</p>

<code>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;
}
</code>
0 голосов
/ 30 декабря 2018

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

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

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

0 голосов
/ 02 августа 2016

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

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...