在 Javascript 中将“float”转换为字节,无需 Float32Array

问题描述 投票:0回答:4

好吧,我遇到了一个相当烦人的情况,我无法访问类型化数组(例如 Float32Array),但仍然需要能够将 Javascript 数字转换为字节。现在,我可以很好地处理整数,但我不知道如何处理浮点值。

我已经解决了以另一种方式(将字节转换为浮点数)的问题,但是有关从浮点数转换为字节的文档非常稀缺,因为大多数语言只是让您读取指针或具有用于处理它的通用类。

理想情况下,我希望能够将浮点数转换为 4 字节和 8 字节表示形式,并选择使用哪一种。然而,可以简单地获取一个数字并将其吐出为 8 字节的代码仍然很棒,因为我可能可以从那里自己想出 32 位版本。

javascript floating-point type-conversion
4个回答
10
投票

好吧,我实际上已经弄清楚了,所以我将分享我的单精度和双精度解决方案。现在我不能保证它们 100% 符合标准,但它们不需要循环并且似乎工作得很好:

单精度(给定十进制值,输出具有二进制表示的单个 32 位大端整数):

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

双精度(给定一个十进制值,输出两个 32 位整数,并以大端顺序表示二进制表示):

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

对复制/粘贴中的任何错误表示歉意,该代码也省略了对字节序的任何处理,尽管添加起来相当容易。

感谢大家提出建议,但我最终主要是自己解决,因为我想尽可能避免循环以提高速度;它仍然不是非常快,但它会做 =)


1
投票

请参阅 BinaryParser.encodeFloat 此处


1
投票

您可以使用 IEEE 754 的 JavaScript 实现,例如 http://ysangkok.github.io/IEEE-754/index.xhtml 中的实现。它使用 Emscripten 和 gmp.js。


0
投票

以下代码应该完全符合规范(支持-0(事实上,之前的答案即使对于正数零也返回了错误的结果),支持正确舍入,与当前投票最多的答案不同,支持次正规数) 。它支持半精度浮点数(16位)、单精度(32位)和双精度(64位)。

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) {
    const r = Math.round(num);
    return (r === num + 0.5) && (r % 2 === 1) ? r - 1 : r;
}

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.floor(Math.log2(value));
    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.floor(Math.log2(value));
    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.floor(Math.log2(value));
    const significand = (value * 2 ** -exponent - 1) * recSignificandDenom64;
    return [neg << 31 | exponent + 1023 << 20 | significand / p32, significand | 0];
}

欢迎性能优化!

© www.soinside.com 2019 - 2024. All rights reserved.