/* * Copyright (c) 2018-2019 Rafael da Silva Rocha. * Copyright (c) 2013 DeNA Co., Ltd. * Copyright (c) 2010, Linden Research, Inc * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ /** * @fileoverview Encode and decode IEEE 754 floating point numbers. * @see https://github.com/rochars/ieee754-buffer * @see https://bitbucket.org/lindenlab/llsd/raw/7d2646cd3f9b4c806e73aebc4b32bd81e4047fdc/js/typedarray.js * @see https://github.com/kazuho/ieee754.js/blob/master/ieee754.js */ /** * @module ieee754-buffer */ /** * A class to encode and decode IEEE 754 floating-point numbers. */ export class IEEE754Buffer { /** * Pack a IEEE 754 floating point number. * @param {number} ebits The exponent bits. * @param {number} fbits The fraction bits. */ constructor(ebits, fbits) { /** * @type {number} * @private */ this.ebits = ebits; /** * @type {number} * @private */ this.fbits = fbits; /** * @type {number} * @private */ this.bias = (1 << (ebits - 1)) - 1; /** * @type {number} * @private */ this.numBytes = Math.ceil((ebits + fbits) / 8); /** * @type {number} * @private */ this.biasP2 = Math.pow(2, this.bias + 1); /** * @type {number} * @private */ this.ebitsFbits = (ebits + fbits); /** * @type {number} * @private */ this.fbias = Math.pow(2, -(8 * this.numBytes - 1 - ebits)); } /** * Pack a IEEE 754 floating point number. * @param {!Uint8Array|!Array} buffer The buffer. * @param {number} num The number. * @param {number} index The index to write on the buffer. * @return {number} The next index to write on the buffer. * @throws {TypeError} If input is not a number. */ pack(buffer, num, index) { // Only numbers can be packed if (typeof num != 'number') { throw new TypeError(); } // Round overflows if (Math.abs(num) > this.biasP2 - (this.ebitsFbits * 2)) { num = num < 0 ? -Infinity : Infinity; } /** * sign, need this to handle negative zero * @see http://cwestblog.com/2014/02/25/javascript-testing-for-negative-zero/ * @type {number} */ let sign = (((num = +num) || 1 / num) < 0) ? 1 : num < 0 ? 1 : 0; num = Math.abs(num); /** @type {number} */ let exp = Math.min(Math.floor(Math.log(num) / Math.LN2), 1023); /** @type {number} */ let fraction = roundToEven(num / Math.pow(2, exp) * Math.pow(2, this.fbits)); // NaN if (num !== num) { fraction = Math.pow(2, this.fbits - 1); exp = (1 << this.ebits) - 1; // Number } else if (num !== 0) { if (num >= Math.pow(2, 1 - this.bias)) { if (fraction / Math.pow(2, this.fbits) >= 2) { exp = exp + 1; fraction = 1; } // Overflow if (exp > this.bias) { exp = (1 << this.ebits) - 1; fraction = 0; } else { exp = exp + this.bias; fraction = roundToEven(fraction) - Math.pow(2, this.fbits); } } else { fraction = roundToEven(num / Math.pow(2, 1 - this.bias - this.fbits)); exp = 0; } } return this.packFloatBits_(buffer, index, sign, exp, fraction); } /** * Unpack a IEEE 754 floating point number. * Derived from IEEE754 by DeNA Co., Ltd., MIT License. * Adapted to handle NaN. Should port the solution to the original repo. * @param {!Uint8Array|!Array} buffer The buffer. * @param {number} index The index to read from the buffer. * @return {number} The floating point number. */ unpack(buffer, index) { /** @type {number} */ let eMax = (1 << this.ebits) - 1; /** @type {number} */ let significand; /** @type {string} */ let leftBits = ""; for (let i = this.numBytes - 1; i >= 0 ; i--) { /** @type {string} */ let t = buffer[i + index].toString(2); leftBits += "00000000".substring(t.length) + t; } /** @type {number} */ let sign = leftBits.charAt(0) == "1" ? -1 : 1; leftBits = leftBits.substring(1); /** @type {number} */ let exponent = parseInt(leftBits.substring(0, this.ebits), 2); leftBits = leftBits.substring(this.ebits); if (exponent == eMax) { if (parseInt(leftBits, 2) !== 0) { return NaN; } return sign * Infinity; } else if (exponent === 0) { exponent += 1; significand = parseInt(leftBits, 2); } else { significand = parseInt("1" + leftBits, 2); } return sign * significand * this.fbias * Math.pow(2, exponent - this.bias); } /** * Pack a IEEE754 from its sign, exponent and fraction bits * and place it in a byte buffer. * @param {!Uint8Array|!Array} buffer The byte buffer to write to. * @param {number} index The buffer index to write. * @param {number} sign The sign. * @param {number} exp the exponent. * @param {number} fraction The fraction. * @return {number} * @private */ packFloatBits_(buffer, index, sign, exp, fraction) { /** @type {!Array} */ let bits = []; // the sign bits.push(sign); // the exponent for (let i = this.ebits; i > 0; i -= 1) { bits[i] = (exp % 2 ? 1 : 0); exp = Math.floor(exp / 2); } // the fraction let len = bits.length; for (let i = this.fbits; i > 0; i -= 1) { bits[len + i] = (fraction % 2 ? 1 : 0); fraction = Math.floor(fraction / 2); } // pack as bytes /** @type {string} */ let str = bits.join(''); /** @type {number} */ let numBytes = this.numBytes + index - 1; /** @type {number} */ let k = index; while (numBytes >= index) { buffer[numBytes] = parseInt(str.substring(0, 8), 2); str = str.substring(8); numBytes--; k++; } return k; } } /** * Round a number to its nearest even value. * @param {number} n The number. * @return {number} * @private */ function roundToEven(n) { /** @type {number} */ let w = Math.floor(n); let f = n - w; if (f < 0.5) { return w; } if (f > 0.5) { return w + 1; } return w % 2 ? w + 1 : w; }