最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

floating point - Convert "float" to bytes in Javascript without Float32Array - Stack Overflow

programmeradmin5浏览0评论

Okay so I'm an a fairly annoying situation where I don't have access to typed arrays such as Float32Array, but still need to be able to convert a Javascript number into bytes. Now, an integer I can handle just fine, but I have no idea how to do it for a floating point value.

I've solved the problem of doing it the other way around (bytes into a float), but documentation on converting from float to bytes is pretty scarce, as most language just let you read the pointer or have mon classes for handling it.

Ideally I'd like to be able to convert floats into both 4-byte and 8-byte representations, and choose which one to use. However, code that can simply take a number and spit it out as 8-bytes would still be great, as I can probably e up with the 32-bit version myself from there.

Okay so I'm an a fairly annoying situation where I don't have access to typed arrays such as Float32Array, but still need to be able to convert a Javascript number into bytes. Now, an integer I can handle just fine, but I have no idea how to do it for a floating point value.

I've solved the problem of doing it the other way around (bytes into a float), but documentation on converting from float to bytes is pretty scarce, as most language just let you read the pointer or have mon classes for handling it.

Ideally I'd like to be able to convert floats into both 4-byte and 8-byte representations, and choose which one to use. However, code that can simply take a number and spit it out as 8-bytes would still be great, as I can probably e up with the 32-bit version myself from there.

Share Improve this question asked Apr 10, 2013 at 20:03 HaravikkHaravikk 3,2902 gold badges34 silver badges52 bronze badges 2
  • 2 toString(2) will help? (42).toString(2) – Mohsen Commented Apr 10, 2013 at 20:06
  • 1 You might also find some use of Number.toExponential – Paul S. Commented Apr 10, 2013 at 20:14
Add a ment  | 

4 Answers 4

Reset to default 10

Okay, so I actually figured it out, so I'll share my solution for single and double precision. Now I can't guarantee that they're 100% standards pliant, but they require no loops and seem to work just fine:

Single precision (given a decimal value outputs a single 32-bit big endian integer with the binary representation):

function toFloat32(value) {
    var bytes = 0;
    switch (value) {
        case Number.POSITIVE_INFINITY: bytes = 0x7F800000; break;
        case Number.NEGATIVE_INFINITY: bytes = 0xFF800000; break;
        case +0.0: bytes = 0x40000000; break;
        case -0.0: bytes = 0xC0000000; break;
        default:
            if (Number.isNaN(value)) { bytes = 0x7FC00000; break; }

            if (value <= -0.0) {
                bytes = 0x80000000;
                value = -value;
            }

            var exponent = Math.floor(Math.log(value) / Math.log(2));
            var significand = ((value / Math.pow(2, exponent)) * 0x00800000) | 0;

            exponent += 127;
            if (exponent >= 0xFF) {
                exponent = 0xFF;
                significand = 0;
            } else if (exponent < 0) exponent = 0;

            bytes = bytes | (exponent << 23);
            bytes = bytes | (significand & ~(-1 << 23));
        break;
    }
    return bytes;
};

Double precision (given a decimal value outputs two 32-bit integers with the binary representation in big-endian order):

function toFloat64(value) {
    if ((byteOffset + 8) > this.byteLength) 
        throw "Invalid byteOffset: Cannot write beyond view boundaries.";

    var hiWord = 0, loWord = 0;
    switch (value) {
        case Number.POSITIVE_INFINITY: hiWord = 0x7FF00000; break;
        case Number.NEGATIVE_INFINITY: hiWord = 0xFFF00000; break;
        case +0.0: hiWord = 0x40000000; break;
        case -0.0: hiWord = 0xC0000000; break;
        default:
            if (Number.isNaN(value)) { hiWord = 0x7FF80000; break; }

            if (value <= -0.0) {
                hiWord = 0x80000000;
                value = -value;
            }

            var exponent = Math.floor(Math.log(value) / Math.log(2));
            var significand = Math.floor((value / Math.pow(2, exponent)) * Math.pow(2, 52));

            loWord = significand & 0xFFFFFFFF;
            significand /= Math.pow(2, 32);

            exponent += 1023;
            if (exponent >= 0x7FF) {
                exponent = 0x7FF;
                significand = 0;
            } else if (exponent < 0) exponent = 0;

            hiWord = hiWord | (exponent << 20);
            hiWord = hiWord | (significand & ~(-1 << 20));
        break;
    }

    return [hiWord, loWord];
};

Apologies for any mistakes in copy/pasting, also the code ommits any handling of endianness, though it's fairly easy to add.

Thanks to everyone posting suggestions, but I ended up figuring out mostly on my own, as I wanted to avoid looping as much as possible for speed; it's still not exactly blazingly fast but it'll do =)

See BinaryParser.encodeFloat here.

You can use a JavaScript implementation of IEEE 754 like the one at http://ysangkok.github.io/IEEE-754/index.xhtml . It uses Emscripten and gmp.js.

The following code should be fully spec-pliant (supports -0 (in fact, the previous answer returned an incorrect result even for positive zero), supports correct rounding unlike currently most upvoted answer, supports subnormal numbers). It supports half-precision floats (16-bit), single-precision (32-bit), and double-precision (64-bit).

const inverseEpsilon = 1 / Number.EPSILON;
const minInfinity16 = (2 - 2 ** -11) * 2 ** 15, minNormal16 = (1 - 2 ** -11) * 2 ** -14, recMinSubnormal16 = 2 ** 10 * 2 ** 14, recSignificandDenom16 = 2 ** 10;
const minInfinity32 = (2 - 2 ** -24) * 2 ** 127, minNormal32 = (1 - 2 ** -24) * 2 ** -126, recMinSubnormal32 = 2 ** 23 * 2 ** 126, recSignificandDenom32 = 2 ** 23;
const minNormal64 = 2 * 2 ** -1023, recSignificandDenom64 = 2 ** 52, recMinSubnormal64Multiplier = 2 ** 1022;
const p32 = 2 ** 32;

function roundTiesToEven(num) {
    return r + inverseEpsilon - inverseEpsilon;
}

function toFloat16(value) {
    if (Number.isNaN(value)) return 0x7e00; // NaN
    if (value === 0) return (1 / value === -Infinity) << 15; // +0 or -0
    const neg = value < 0;
    if (neg) value = -value;
    if (value >= minInfinity16) return neg << 15 | 0x7c00; // Infinity
    if (value < minNormal16) return neg << 15 | roundTiesToEven(value * recMinSubnormal16); // subnormal
    // normal
    const exponent = Math.log2(value) | 0;
    if (exponent === -15) return neg << 15 | recSignificandDenom16; // we round from a value between 2 ** -15 * (1 + 1022/1024) (the largest subnormal) and 2 ** -14 * (1 + 0/1024) (the smallest normal) to the latter (former impossible because of the subnormal check above)
    const significand = roundTiesToEven((value * 2 ** -exponent - 1) * recSignificandDenom16);
    if (significand === recSignificandDenom16) return neg << 15 | exponent + 16 << 10; // we round from a value between 2 ** n * (1 + 1023/1024) and 2 ** (n + 1) * (1 + 0/1024) to the latter
    return neg << 15 | exponent + 15 << 10 | significand;
}

function toFloat32(value) {
    if (Number.isNaN(value)) return 0x7fc00000; // NaN
    if (value === 0) return (1 / value === -Infinity) << 31; // +0 or -0
    const neg = value < 0;
    if (neg) value = -value;
    if (value >= minInfinity32) return neg << 31 | 0x7f800000; // Infinity
    if (value < minNormal32) return neg << 31 | roundTiesToEven(value * recMinSubnormal32); // subnormal
    // normal
    const exponent = Math.log2(value) | 0;
    if (exponent === -127) return neg << 31 | recSignificandDenom32;
    const significand = roundTiesToEven((value * 2 ** -exponent - 1) * recSignificandDenom32);
    if (significand === recSignificandDenom32) return neg << 31 | exponent + 128 << 23;
    return neg << 31 | exponent + 127 << 23 | significand;
}

function toFloat64(value) { // big-endian
    if (Number.isNaN(value)) return [0x7ff80000, 0]; // NaN
    if (value === 0) return [(1 / value === -Infinity) << 31, 0]; // +0 or -0
    const neg = value < 0;
    if (neg) value = -value;
    if (value === Infinity) return [neg << 31 | 0x7ff00000, 0]; // Infinity
    if (value < minNormal64) { // subnormal
        const significand = value * recSignificandDenom64 * recMinSubnormal64Multiplier;
        return [neg << 31 | significand / p32, significand | 0];
    }
    // normal
    const exponent = Math.log2(value) | 0;
    const significand = (value * 2 ** -exponent - 1) * recSignificandDenom64;
    return [neg << 31 | exponent + 1023 << 20 | significand / p32, significand | 0];
}

Performance optimizations wele!

(roundTiesToEven code taken from core-js)

发布评论

评论列表(0)

  1. 暂无评论