commit ed16e848006b690c58f158676030c08fa9f149b3 Author: Thomas FORGIONE Date: Tue Apr 4 09:50:06 2017 +0200 Initial commit diff --git a/example.js b/example.js new file mode 100644 index 0000000..20257fa --- /dev/null +++ b/example.js @@ -0,0 +1,29 @@ +const o1 = require('./o1.js'); + +let value = Math.PI; +let value2 = Math.exp(1); + +let buffer = o1.encodeArray([ + new o1.Float32(value), + new o1.Float16(value2) +]); + +let buffView = new DataView(buffer); + +let buff1 = new ArrayBuffer(3); +let view1 = new DataView(buff1); +view1.setUint8(0, buffView.getUint8(0)); +view1.setUint8(1, buffView.getUint8(1)); +view1.setUint8(2, buffView.getUint8(2)); + +let buff2 = new ArrayBuffer(1); +let view2 = new DataView(buff2); +view2.setUint8(0, buffView.getUint8(3)); + +let buff3 = new ArrayBuffer(2); +let view3 = new DataView(buff3); +view3.setUint8(0, buffView.getUint8(4)); +view3.setUint8(1, buffView.getUint8(5)); + +let elt = o1.decodeArray([buff1, buff2, buff3], [o1.Float32, o1.Float16]); +console.log(elt[0], elt[1]); diff --git a/o1.js b/o1.js new file mode 100644 index 0000000..fea9f5e --- /dev/null +++ b/o1.js @@ -0,0 +1,316 @@ +o1 = (function() { + + // Float8 + const exponentSize = 5; + const mantissaSize = 10; + const bias = Math.pow(2, exponentSize - 1) - 1; + + // http://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript#8796597 + function decodeFloat16 (binary) { + var exponent = (binary & 0x7C00) >> 10; + fraction = binary & 0x03FF; + return (binary >> 15 ? -1 : 1) * ( + exponent ? + ( + exponent === 0x1F ? + fraction ? NaN : Infinity : + Math.pow(2, exponent - 15) * (1 + fraction / 0x400) + ) : + 6.103515625e-5 * (fraction / 0x400) + ); + }; + + function encodeFloat16(floating) { + let abs = Math.floor(Math.abs(floating)); + let frac = Math.abs(floating) - abs; + let fraction = []; + + let sign = Math.sign(floating) < 0; + + // Compute the integral + let integral = new Number(abs).toString(2).split('').map((a) => a == 1); + + if (integral.length === 1 && integral[0] === false) + integral = []; + + // Compute the fraction + while (fraction.length < 16) { + frac *= 2; + fraction.push(frac >= 1); + if (frac >= 1) { + frac--; + } + if (frac == 0) { + break; + } + } + + // Compute the exponent + let exponent = 0; + while (integral.length > 1) { + fraction = [integral.pop(), ...fraction]; + exponent++; + } + while (integral.length < 1 || !integral[integral.length-1] && fraction.length > 0) { + integral = [...integral, fraction.shift()]; + exponent--; + } + + exponent += bias; // Math.pow(2, 5 - 1) - 1, the bias + + // To array of bools + exponent = new Number(exponent).toString(2).split('').map((a) => a == 1); + + // Pad the exponent with zeros + while (exponent.length < exponentSize) { + exponent = [false, ...exponent]; + } + + // Pad the fraction with zeros + while (fraction.length < mantissaSize) { + fraction.push(false); + } + fraction = fraction.slice(0, mantissaSize); + + + let result = [sign, ...exponent, ...fraction]; + return parseInt(result.reduce((a,b) => a + (b ? '1' : '0'), ''), 2); + + } + + let o1 = {}; + + o1.encodeFloat16 = encodeFloat16; + o1.decodeFloat16 = decodeFloat16; + + o1.IncompleteBufferError = class extends Error {}; + + function newCall(Cls) { + return new (Function.prototype.bind.apply(Cls, arguments)); + } + + o1.BinaryElement = class { + + constructor(value) { + this.value = value; + } + + loadFromBytes(buffers, offset) { + + if (buffers instanceof ArrayBuffer) { + buffers = [buffers]; + } else if (! buffers instanceof Array) { + throw new TypeError("Expecting buffer or array of buffers"); + } + + // Find correct buffer + let currentBufferIndex = 0; + + while (offset >= buffers[currentBufferIndex].byteLength) { + offset -= buffers[currentBufferIndex].byteLength; + currentBufferIndex++; + + if (buffers[currentBufferIndex] === undefined) + throw new o1.IncompleteBufferError(); + } + + let buffer = buffers[currentBufferIndex]; + + let view = new DataView(buffer, offset); + + let bytes = new ArrayBuffer(this.size()); + let bytesView = new DataView(bytes); + + let offsetBetweenBuffers = 0; + + for (let byteIndex = 0; byteIndex < this.size(); byteIndex++) { + + if (byteIndex - offsetBetweenBuffers >= view.byteLength) { + + // Change view + currentBufferIndex++; + let newBuffer = buffers[currentBufferIndex]; + if (newBuffer === undefined) { + throw new o1.IncompleteBufferError(); + } + + view = new DataView(newBuffer); + offsetBetweenBuffers = byteIndex; + + } + + bytesView.setUint8(byteIndex, view.getUint8(byteIndex - offsetBetweenBuffers)); + + } + + this.readFromView(bytesView); + } + + } + + o1.Uint8 = class extends o1.BinaryElement { + + constructor(value) { + super(value); + } + + size() { + return 1; + } + + writeToView(view, offset = 0) { + view.setUint8(offset, this.value); + } + + readFromView(view, offset) { + return this.value = view.getUint8(offset); + } + + } + + o1.Uint32 = class extends o1.BinaryElement { + + constructor(value) { + super(value); + } + + size() { + return 4; + } + + writeToView(view, offset = 0) { + view.setUint32(offset, this.value); + } + + readFromView(view, offset) { + return this.value = view.getUint32(offset); + } + + } + + + o1.Float16 = class extends o1.BinaryElement { + + constructor(value) { + super(value); + } + + size() { + return 2; + } + + writeToView(view, offset = 0) { + view.setUint16(offset, encodeFloat16(this.value)); + } + + readFromView(view, offset) { + let uint16 = view.getUint16(offset); + this.value = decodeFloat16(uint16); + } + + } + + o1.Float32 = class extends o1.BinaryElement { + + constructor(value) { + super(value); + } + + size() { + return 4; + } + + writeToView(view, offset = 0) { + view.setFloat32(offset, this.value); + } + + readFromView(view, offset) { + return this.value = view.getFloat32(offset); + } + + } + + o1.BufferWriter = class { + + constructor(size) { + this.buffer = new ArrayBuffer(size); + this.offset = 0; + } + + write(element) { + + element.writeToView(new DataView(this.buffer, this.offset)); + this.offset += element.size(); + + } + + } + + o1.BufferReader = class { + + constructor(buffers) { + if (buffers instanceof ArrayBuffer) { + this.buffers = [buffers]; + } else if (buffers instanceof Array) { + this.buffers = buffers; + } else { + throw new TypeError("Expexting buffer or array of buffers"); + } + this.offset = 0; + this.array = []; + } + + decodeElement(type) { + let elt = newCall(type); + elt.loadFromBytes(this.buffers, this.offset); + this.offset += elt.size(); + this.array.push(elt); + return elt; + } + + } + + o1.decodeElement = function(buffer, offset, type) { + + let elt = newCall(type); + elt.loadFromBytes(buffer, offset); + return elt; + + } + + o1.encodeArray = function(array) { + + let size = array.map((a) => a.size()).reduce((a,b) => a+b, 0); + + let writer = new o1.BufferWriter(size); + + for (let elt of array) { + writer.write(elt); + } + + return writer.buffer; + + } + + o1.decodeArray = function(buffer, types) { + + let reader = new o1.BufferReader(buffer); + + for (let i = 0; i < types.length; i++) { + + reader.decodeElement(types[i]); + + } + + return reader.array; + + } + + return o1; + +})(); + +if (typeof module !== 'undefined' && module.exports) { + module.exports = o1; +} + diff --git a/package.json b/package.json new file mode 100644 index 0000000..77bc450 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "node-o1", + "version": "1.0.0", + "description": "A small library for writing binary files", + "main": "o1.js", + "repository": { + "type": "git", + "url": "https://gogs.tforgione.fr/tforgione-phd/node-o1" + }, + "keywords": [ + "node", + "binary" + ], + "author": "Thomas Forgione", + "license": "MIT" +}