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

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

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

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

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

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

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

7 ответов


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

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


используя 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");

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

экспонента в основном будет полом логарифма 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), и явно протестировал его отдельно на положительных и отрицательных бесконечностях.

здесь используются только примитивные операции сдвиг влево, двойное умножение, двойное деление и тестирование infinity и NaN, которое поддерживает Javascript, насколько мне известно.


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

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

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

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

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


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

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