Skip to content

Commit

Permalink
Add functions for encoding IEEE-754 floating-points
Browse files Browse the repository at this point in the history
  • Loading branch information
Alhadis committed Feb 22, 2020
1 parent e929be9 commit 6e817a0
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 0 deletions.
7 changes: 7 additions & 0 deletions index.d.ts
Expand Up @@ -28,6 +28,8 @@ export declare function base64Encode(bytes: number[]): string;
export declare function bindMethods(subject: object): object;
export declare function buildDict(dl: HTMLDListElement, valueKey?: boolean, filter?: Function | RegExp): object;
export declare function byteCount(value: number, byteSize?: number): number;
export declare function bytesToFloat32(bytes: number[], littleEndian?: boolean): Float32Array;
export declare function bytesToFloat64(bytes: number[], littleEndian?: boolean): Float64Array;
export declare function bytesToInt16(bytes: number[], littleEndian?: boolean): Int16Array;
export declare function bytesToInt32(bytes: number[], littleEndian?: boolean): Int32Array;
export declare function bytesToInt64(bytes: number[], littleEndian?: boolean): BigInt64Array;
Expand Down Expand Up @@ -77,6 +79,10 @@ export declare function hslToRGB(input: HSLColour): RGBColour;
export declare function hsvToHSL(input: HSVColour): HSLColour;
export declare function hsvToRGB(input: HSVColour): RGBColour;
export declare function injectWordBreaks(element: Element, limit?: number): HTMLElement[];
export declare function int16ToBytes(input: number | number[], littleEndian?: boolean): Uint8Array;
export declare function int32ToBytes(input: number | number[], littleEndian?: boolean): Uint8Array;
export declare function int64ToBytes(input: bigint | bigint[], littleEndian?: boolean): Uint8Array;
export declare function int8ToBytes(input: number | number[]): Uint8Array;
export declare function isByteArray(input: any): boolean;
export declare function isFixedWidth(font: string): boolean;
export declare function isIE(version: string, operand: string): boolean;
Expand All @@ -92,6 +98,7 @@ export declare function keyGrep(subject: object, pattern: RegExp | string): obje
export declare function ls(paths?: string[], options?: {filter?: RegExp | Function; ignore?: RegExp | Function; recurse?: number; followSymlinks?: boolean}): Promise<Map<string, fs.Stats>>;
export declare function nearest(subject: Node, selector: string, ignoreSelf?: boolean): Element;
export declare function nerf(fn: Function, context?: object): Function;
export declare function normalise(value: number): number[];
export declare function ordinalSuffix(n: number): string;
export declare function parseCSSDuration(value: string): number;
export declare function parseHTMLFragment(input: string): Node[];
Expand Down
80 changes: 80 additions & 0 deletions lib/binary.mjs
Expand Up @@ -19,6 +19,86 @@ export function adler32(bytes){
}


/**
* Convert bytes to 32-bit IEEE 754 floating-point values.
*
* @example bytesToFloat32([0x41, 0xC8, 0x00, 0x00]) == [25];
* @param {Number[]} bytes
* @param {Boolean} [littleEndian=false]
* @return {Float32Array}
*/
export function bytesToFloat32(bytes, littleEndian = false){
const {length} = bytes;
const floats = new Float32Array(Math.ceil(length / 4));
for(let i = 0; i < length; i += 4){
let a = bytes[i] || 0;
let b = bytes[i + 1] || 0;
let c = bytes[i + 2] || 0;
let d = bytes[i + 3] || 0;
if(littleEndian) [a, b, c, d] = [d, c, b, a];
const sign = (-1) ** +!!(a & 128);
const expo = (a & 127) << 1 | (b & 128) >>> 7;
let frac = (b & 127) << 16 | c << 8 | d;
d = ~~(i / 4);
switch(expo){
case 0xFF: floats[d] = 0 === frac ? sign * Infinity : NaN; break;
case 0x00: if((floats[d] = (sign * 0)) === frac) break; // Fall-through
default:
if(expo) frac |= 1 << 23;
let float = 0;
for(let i = 0; i < 24; float += (frac >> 23 - i & 1) * 2 ** -i++);
floats[d] = float * 2 ** (expo ? expo - 127 : -126) * sign;
}
}
return floats;
}


/**
* Convert bytes to 64-bit IEEE 754 floating-point values.
*
* @example bytesToFloat64([0x40,0x37,0,0,0,0,0,0]) == [23];
* @param {Number[]} bytes
* @param {Boolean} [littleEndian=false]
* @return {Float64Array}
*/
export function bytesToFloat64(input, littleEndian = false){
const {length} = input;
const floats = new Float64Array(Math.ceil(length / 8));
for(let i = 0; i < length; i += 8){
let a = input[i] || 0;
let b = input[i + 1] || 0;
let c = input[i + 2] || 0;
let d = input[i + 3] || 0;
let e = input[i + 4] || 0;
let f = input[i + 5] || 0;
let g = input[i + 6] || 0;
let h = input[i + 7] || 0;
if(littleEndian) [a, b, c, d, e, f, g, h] = [h, g, f, e, d, c, b, a];
const sign = (-1) ** +!!(a & 128);
const expo = (a & 127) << 4 | (b & 240) >>> 4;
let frac = BigInt(b & 15) << 48n
| BigInt(c) << 40n
| BigInt(d) << 32n
| BigInt(e) << 24n
| BigInt(f) << 16n
| BigInt(g) << 8n
| BigInt(h);
h = ~~(i / 8);
switch(expo){
case 0x7FF: floats[h] = 0n === frac ? sign * Infinity : NaN; break;
case 0x000: if((floats[h] = (sign * 0)) === Number(frac)) break; // Fall-through
default:
if(expo) frac |= 1n << 52n;
let float = 0;
for(let i = 0; i < 53; float += Number(frac >> 52n - BigInt(i) & 1n) * 2 ** -i++);
floats[h] = float * 2 ** (expo ? expo - 1023 : -126) * sign;
}
}
return floats;
}


/**
* Convert bytes to 8-bit signed integers.
*
Expand Down
97 changes: 97 additions & 0 deletions test/binary.mjs
Expand Up @@ -51,6 +51,103 @@ describe("Byte-level functions", () => {
});
});

describe("bytesToFloat32()", () => {
const {bytesToFloat32} = utils;
const encode = (input, expected, thresh = 0) => {
if(thresh){
expect(bytesToFloat32(input)[0]).to.be.closeTo(expected[0], thresh);
expect(bytesToFloat32(input.reverse(), true)[0]).to.be.closeTo(expected[0], thresh);
}
else{
expected = Float32Array.from(expected);
expect(bytesToFloat32(input, false)).to.eql(expected);
expect(bytesToFloat32(input.reverse(), true)).to.eql(expected);
}
};
it("encodes positive numbers", () => {
encode([0x3F, 0x80, 0x00, 0x00], [1]);
encode([0x41, 0xC8, 0x00, 0x00], [25]);
encode([0x3F, 0x80, 0x00, 0x01], [1.0000001192], 1e-11);
});
it("encodes negative numbers", () => encode([0xC0, 0x00, 0x00, 0x00], [-2]));
it("encodes positive zero", () => encode([0x00, 0x00, 0x00, 0x00], [0]));
it("encodes negative zero", () => encode([0x80, 0x00, 0x00, 0x00], [-0]));
it("encodes positive infinity", () => encode([0x7F, 0x80, 0x00, 0x00], [Infinity]));
it("encodes negative infinity", () => encode([0xFF, 0x80, 0x00, 0x00], [-Infinity]));
it("roughly approximates π", () => encode([0x40, 0x49, 0x0F, 0xDB], [3.14159274101], 1e-11));
it("encodes subnormal numbers", () => {
encode([0x00, 0x00, 0x00, 0x01], [1.4012984643 * (10 ** -45)], 1e-11);
encode([0x00, 0x7F, 0xFF, 0xFF], [1.1754942107 * (10 ** -38)], 1e-11);
encode([0x3F, 0x7F, 0xFF, 0xFF], [0.9999999404], 1e-11);
encode([0x3E, 0xAA, 0xAA, 0xAB], [0.333333343267], 1e-11);
});
it("encodes NaN", () => {
encode([0xFF, 0xC0, 0x00, 0x01], [NaN]);
encode([0xFF, 0x80, 0x00, 0x01], [NaN]);
});
it("zero-fills missing bytes", () => {
const expected = new Float32Array([-2]);
expect(bytesToFloat32([0xC0])).to.eql(expected);
expect(bytesToFloat32([0xC0, 0x00])).to.eql(expected);
expect(bytesToFloat32([0xC0, 0x00, 0x00])).to.eql(expected);
expect(bytesToFloat32([0xC0, 0x00, 0x00, 0x00, 0xC0])).to.eql(new Float32Array([-2, -2]));
});
});

describe("bytesToFloat64()", () => {
const {bytesToFloat64} = utils;
const encode = (input, expected, thresh = 0) => {
if(thresh){
expect(bytesToFloat64(input)[0]).to.be.closeTo(expected[0], thresh);
expect(bytesToFloat64(input.reverse(), true)[0]).to.be.closeTo(expected[0], thresh);
}
else{
expected = Float64Array.from(expected);
expect(bytesToFloat64(input)).to.eql(expected);
expect(bytesToFloat64(input.reverse(), true)).to.eql(expected);
}
};
it("encodes positive numbers", () => {
encode([0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [1]);
encode([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [2]);
encode([0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [3]);
encode([0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [4]);
encode([0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [5]);
encode([0x40, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [6]);
encode([0x40, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [23]);
encode([0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], [1.0000000000000002]);
encode([0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02], [1.0000000000000004]);
encode([0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], [1.7976931348623157 * (10 ** 308)]);
});
it("encodes negative numbers", () => encode([0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [-2]));
it("encodes positive zero", () => encode([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [+0]));
it("encodes negative zero", () => encode([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [-0]));
it("encodes positive infinity", () => encode([0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [Infinity]));
it("encodes negative infinity", () => encode([0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [-Infinity]));
it("roughly approximates π", () => encode([0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18], [Math.PI]));
it("encodes subnormal numbers", () => {
encode([0x3F, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0.01171875]);
encode([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], [2 ** -1074], Number.EPSILON);
encode([0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], [2.2250738585072009 * (10 ** -308)], Number.EPSILON);
encode([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [2.2250738585072014 * (10 ** -308)], Number.EPSILON);
encode([0x3F, 0xD5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55], [1 / 3]);
});
it("encodes NaN", () => {
encode([0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], [NaN]);
encode([0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], [NaN]);
encode([0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], [NaN]);
});
it("zero-fills missing bytes", () => {
const input = [0xC0];
for(let i = 0; i < 7; ++i){
expect(bytesToFloat64(input)).to.eql(Float64Array.from([-2]));
input.push(0x00);
}
input.push(0xC0);
expect(bytesToFloat64(input)).to.eql(Float64Array.from([-2, -2]));
});
});

describe("bytesToInt8()", () => {
const {bytesToInt8} = utils;
it("encodes positive integers up to +127", () => {
Expand Down

0 comments on commit 6e817a0

Please sign in to comment.