好吧,我遇到了一个相当烦人的情况,我无法访问类型化数组(例如 Float32Array),但仍然需要能够将 Javascript 数字转换为字节。现在,我可以很好地处理整数,但我不知道如何处理浮点值。
我已经解决了以另一种方式(将字节转换为浮点数)的问题,但是有关从浮点数转换为字节的文档非常稀缺,因为大多数语言只是让您读取指针或具有用于处理它的通用类。
理想情况下,我希望能够将浮点数转换为 4 字节和 8 字节表示形式,并选择使用哪一种。然而,可以简单地获取一个数字并将其吐出为 8 字节的代码仍然很棒,因为我可能可以从那里自己想出 32 位版本。
好吧,我实际上已经弄清楚了,所以我将分享我的单精度和双精度解决方案。现在我不能保证它们 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];
};
对复制/粘贴中的任何错误表示歉意,该代码也省略了对字节序的任何处理,尽管添加起来相当容易。
感谢大家提出建议,但我最终主要是自己解决,因为我想尽可能避免循环以提高速度;它仍然不是非常快,但它会做 =)
请参阅 BinaryParser.encodeFloat 此处。
您可以使用 IEEE 754 的 JavaScript 实现,例如 http://ysangkok.github.io/IEEE-754/index.xhtml 中的实现。它使用 Emscripten 和 gmp.js。
以下代码应该完全符合规范(支持-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];
}
欢迎性能优化!