Skip to content

Commit

Permalink
Layout: add 64-bit integral encodings
Browse files Browse the repository at this point in the history
In many cases 64-bit integral encodings hold values that are less than
2^53 and so can be exactly represented as a JavaScript number.  Simplify
use of these fields by having a Number representation for them, with a
slightly different naming convention to highlight that they are not
exactly conversions.

Closes #13
  • Loading branch information
pabigot committed Nov 29, 2015
1 parent 2ab3c5d commit 2679991
Show file tree
Hide file tree
Showing 5 changed files with 421 additions and 0 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Layout support is provided for these types of data:

* Signed and unsigned integral values from 1 to 6 bytes in length, in
little-endian or big-endian format;
* Signed and unsigned 64-bit integral values decoded as integral
Numbers;
* Float and double values (also little-endian or big-endian);
* Sequences of instances of an arbitrary layout;
* Structures with named fields containing arbitrary layouts;
Expand Down Expand Up @@ -164,6 +166,27 @@ The buffer-layout way:

See [BitStructure](http://pabigot.github.io/buffer-layout/module-Layout-BitStructure.html).

### 64-bit values as Numbers

The C definition:

uint64_t v = 0x0102030405060708ULL;

The buffer-layout way:

var ds = lo.nu64be(),
b = Buffer('0102030405060708', 'hex'),
v = 72623859790382856,
nv = v - 6;
assert.equal(v, nv);
assert.equal(ds.decode(b), nv);

Note that because the exact value is not less than 2^53 it cannot be
represented as a JavaScript Number, and is instead approximated by a
nearby representable integer that is equivalent within Numbers.

See [NearUInt64](http://pabigot.github.io/buffer-layout/module-Layout-NearUInt64BE.html).

### A NUL-terminated C string

The C definition:
Expand Down
182 changes: 182 additions & 0 deletions lib/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
* module:Layout.s24be|24-bit}, {@link module:Layout.s32be|32-bit},
* {@link module:Layout.s40be|40-bit}, and {@link
* module:Layout.s48be|48-bit} representation ranges;
* * 64-bit integral values that decode to an exact (if magnitude is
* less than 2^53) or nearby integral Number in {@link
* module:Layout.nu64|unsigned little-endian}, {@link
* module:Layout.nu64be|unsigned big-endian}, {@link
* module:Layout.ns64|signed little-endian}, and {@link
* module:Layout.ns64be|unsigned big-endian} encodings;
* * 32-bit floating point values with {@link
* module:Layout.f32|little-endian} and {@link
* module:Layout.f32be|big-endian} representations;
Expand Down Expand Up @@ -92,6 +98,10 @@
* @local UIntBE
* @local Int
* @local IntBE
* @local NearUInt64
* @local NearUInt64BE
* @local NearInt64
* @local NearInt64BE
* @local Float
* @local FloatBE
* @local Double
Expand Down Expand Up @@ -531,6 +541,162 @@ IntBE.prototype.encode = function (src, b, offset) {
b.writeIntBE(src, offset, this.span);
};

var V2E32 = Math.pow(2, 32);
/* True modulus high and low 32-bit words, where low word is always
* non-negative. */
function divmodInt64 (src) {
var hi32 = Math.floor(src / V2E32),
lo32 = src - (hi32 * V2E32);
//assert.equal(roundedInt64(hi32, lo32), src);
//assert(0 <= lo32);
return { hi32: hi32,
lo32: lo32 };
}
/* Reconstruct Number from quotient and non-negative remainder */
function roundedInt64 (hi32, lo32) {
return hi32 * V2E32 + lo32;
}

/** Represent an unsigned 64-bit integer in little-endian format when
* encoded and as a near integral JavaScript Number when decoded.
*
* *Factory*: {@link module:Layout.nu64|nu64}
*
* **NOTE** Values with magnitude greater than 2^52 may not decode to
* the exact value of the encoded representation.
*
* @constructor
* @augments {Layout} */
function NearUInt64 (property) {
Layout.call(this, 8, property);
Object.freeze(this);
}
NearUInt64.prototype = Object.create(Layout.prototype);
NearUInt64.prototype.constructor = NearUInt64;
/** Implement {@link Layout#decode|decode} for {@link NearUInt64|NearUInt64}. */
NearUInt64.prototype.decode = function (b, offset) {
if (undefined === offset) {
offset = 0;
}
var lo32 = b.readUInt32LE(offset),
hi32 = b.readUInt32LE(offset+4);
return roundedInt64(hi32, lo32);
};
/** Implement {@link Layout#encode|encode} for {@link NearUInt64|NearUInt64}. */
NearUInt64.prototype.encode = function (src, b, offset) {
if (undefined === offset) {
offset = 0;
}
var split = divmodInt64(src);
b.writeUInt32LE(split.lo32, offset);
b.writeUInt32LE(split.hi32, offset+4);
};

/** Represent an unsigned 64-bit integer in big-endian format when
* encoded and as a near integral JavaScript Number when decoded.
*
* *Factory*: {@link module:Layout.nu64be|nu64be}
*
* **NOTE** Values with magnitude greater than 2^52 may not decode to
* the exact value of the encoded representation.
*
* @constructor
* @augments {Layout} */
function NearUInt64BE (property) {
Layout.call(this, 8, property);
Object.freeze(this);
}
NearUInt64BE.prototype = Object.create(Layout.prototype);
NearUInt64BE.prototype.constructor = NearUInt64BE;
/** Implement {@link Layout#decode|decode} for {@link NearUInt64BE|NearUInt64BE}. */
NearUInt64BE.prototype.decode = function (b, offset) {
if (undefined === offset) {
offset = 0;
}
var hi32 = b.readUInt32BE(offset),
lo32 = b.readUInt32BE(offset+4);
return roundedInt64(hi32, lo32);
};
/** Implement {@link Layout#encode|encode} for {@link NearUInt64BE|NearUInt64BE}. */
NearUInt64BE.prototype.encode = function (src, b, offset) {
if (undefined === offset) {
offset = 0;
}
var split = divmodInt64(src);
b.writeUInt32BE(split.hi32, offset);
b.writeUInt32BE(split.lo32, offset+4);
};

/** Represent a signed 64-bit integer in little-endian format when
* encoded and as a near integral JavaScript Number when decoded.
*
* *Factory*: {@link module:Layout.ns64|ns64}
*
* **NOTE** Values with magnitude greater than 2^52 may not decode to
* the exact value of the encoded representation.
*
* @constructor
* @augments {Layout} */
function NearInt64 (property) {
Layout.call(this, 8, property);
Object.freeze(this);
}
NearInt64.prototype = Object.create(Layout.prototype);
NearInt64.prototype.constructor = NearInt64;
/** Implement {@link Layout#decode|decode} for {@link NearInt64|NearInt64}. */
NearInt64.prototype.decode = function (b, offset) {
if (undefined === offset) {
offset = 0;
}
var lo32 = b.readUInt32LE(offset),
hi32 = b.readInt32LE(offset+4);
return roundedInt64(hi32, lo32);
};
/** Implement {@link Layout#encode|encode} for {@link NearInt64|NearInt64}. */
NearInt64.prototype.encode = function (src, b, offset) {
if (undefined === offset) {
offset = 0;
}
var split = divmodInt64(src);
b.writeUInt32LE(split.lo32, offset);
b.writeInt32LE(split.hi32, offset+4);
};

/** Represent a signed 64-bit integer in big-endian format when
* encoded and as a near integral JavaScript Number when decoded.
*
* *Factory*: {@link module:Layout.ns64be|ns64be}
*
* **NOTE** Values with magnitude greater than 2^52 may not decode to
* the exact value of the encoded representation.
*
* @constructor
* @augments {Layout} */
function NearInt64BE (property) {
Layout.call(this, 8, property);
Object.freeze(this);
}
NearInt64BE.prototype = Object.create(Layout.prototype);
NearInt64BE.prototype.constructor = NearInt64BE;
/** Implement {@link Layout#decode|decode} for {@link NearInt64BE|NearInt64BE}. */
NearInt64BE.prototype.decode = function (b, offset) {
if (undefined === offset) {
offset = 0;
}
var hi32 = b.readInt32BE(offset),
lo32 = b.readUInt32BE(offset+4);
return roundedInt64(hi32, lo32);
};
/** Implement {@link Layout#encode|encode} for {@link NearInt64BE|NearInt64BE}. */
NearInt64BE.prototype.encode = function (src, b, offset) {
if (undefined === offset) {
offset = 0;
}
var split = divmodInt64(src);
b.writeInt32BE(split.hi32, offset);
b.writeUInt32BE(split.lo32, offset+4);
};

/** Represent a 32-bit floating point number in little-endian format.
*
* *Factory*: {@link module:Layout.f32|f32}
Expand Down Expand Up @@ -1892,6 +2058,10 @@ exports.u40 = function (property) { return new UInt(5, property); };
* spanning six bytes. */
exports.u48 = function (property) { return new UInt(6, property); };

/** Factory for {@link NearUInt64|little-endian unsigned int
* layouts} interpreted as Numbers. */
exports.nu64 = function (property) { return new NearUInt64(property); };

/** Factory for {@link UInt|big-endian unsigned int layouts}
* spanning two bytes. */
exports.u16be = function (property) { return new UIntBE(2, property); };
Expand All @@ -1912,6 +2082,10 @@ exports.u40be = function (property) { return new UIntBE(5, property); };
* spanning six bytes. */
exports.u48be = function (property) { return new UIntBE(6, property); };

/** Factory for {@link NearUInt64BE|big-endian unsigned int
* layouts} interpreted as Numbers. */
exports.nu64be = function (property) { return new NearUInt64BE(property); };

/** Factory for {@link Int|signed int layouts} spanning one
* byte. */
exports.s8 = function (property) { return new Int(1, property); };
Expand All @@ -1936,6 +2110,10 @@ exports.s40 = function (property) { return new Int(5, property); };
* spanning six bytes. */
exports.s48 = function (property) { return new Int(6, property); };

/** Factory for {@link NearInt64|little-endian signed int layouts}
* interpreted as Numbers. */
exports.ns64 = function (property) { return new NearInt64(property); };

/** Factory for {@link Int|big-endian signed int layouts}
* spanning two bytes. */
exports.s16be = function (property) { return new IntBE(2, property); };
Expand All @@ -1956,6 +2134,10 @@ exports.s40be = function (property) { return new IntBE(5, property); };
* spanning six bytes. */
exports.s48be = function (property) { return new IntBE(6, property); };

/** Factory for {@link NearInt64BE|big-endian signed int layouts}
* interpreted as Numbers. */
exports.ns64be = function (property) { return new NearInt64BE(property); };

/** Factory for {@link Float|little-endian 32-bit floating point} values. */
exports.f32 = function (property) { return new Float(property); };

Expand Down
Loading

0 comments on commit 2679991

Please sign in to comment.