Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Support for toNumber(allowImprecise), improved tests, bump version

  • Loading branch information...
commit 73dc2a3bfaa4b6b16061161f0752f5c33f3c7c24 1 parent 39791af
@broofa authored
Showing with 104 additions and 69 deletions.
  1. +83 −53 Int64.js
  2. +1 −1  package.json
  3. +20 −15 test.js
View
136 Int64.js
@@ -1,26 +1,33 @@
/**
-* Support for handling 64-bit int numbers in Javascript (node.js)
-*
-* JS Numbers are IEEE-754 binary double-precision floats, which limits the
-* range of values that can be represented with integer precision to:
-*
-* 2^^53 <= N <= 2^53
-*
-* Int64 objects wrap a node Buffer that holds the 8-bytes of int64 data. These
-* objects operate directly on the buffer which means that if they are created
-* using an existing buffer then setting the value will modify the Buffer, and
-* vice-versa.
-*
-* For details about IEEE-754 see:
-* http://en.wikipedia.org/wiki/Double_precision_floating-point_format
-*/
-
-// Useful masks and values for doing bit twiddling
+ * Support for handling 64-bit int numbers in Javascript (node.js)
+ *
+ * JS Numbers are IEEE-754 binary double-precision floats, which limits the
+ * range of values that can be represented with integer precision to:
+ *
+ * 2^^53 <= N <= 2^53
+ *
+ * Int64 objects wrap a node Buffer that holds the 8-bytes of int64 data. These
+ * objects operate directly on the buffer which means that if they are created
+ * using an existing buffer then setting the value will modify the Buffer, and
+ * vice-versa.
+ *
+ * Internal Representation
+ *
+ * The internal buffer format is Big Endian. I.e. the most-significant byte is
+ * at buffer[0], the least-significant at buffer[7]. For the purposes of
+ * converting to/from JS native numbers, the value is assumed to be a signed
+ * integer stored in 2's complement form.
+ *
+ * For details about IEEE-754 see:
+ * http://en.wikipedia.org/wiki/Double_precision_floating-point_format
+ */
+
+// Useful masks and values for bit twiddling
var MASK31 = 0x7fffffff, VAL31 = 0x80000000;
var MASK32 = 0xffffffff, VAL32 = 0x100000000;
// Map for converting hex octets to strings
-var _HEX = [], _PADHEX = [];
+var _HEX = [];
for (var i = 0; i < 256; i++) {
_HEX[i] = (i > 0xF ? '' : '0') + i.toString(16);
}
@@ -30,13 +37,13 @@ for (var i = 0; i < 256; i++) {
//
/**
-* Constructor accepts the following arguments:
-*
-* new Int64(buffer[, offset=0]) - Existing Buffer with byte offset
-* new Int64(string) - Hex string (throws if n is outside int64 range)
-* new Int64(number) - Number (throws if n is outside int64 range)
-* new Int64(hi, lo) - Raw bits as two 32-bit values
-*/
+ * Constructor accepts any of the following argument types:
+ *
+ * new Int64(buffer[, offset=0]) - Existing Buffer with byte offset
+ * new Int64(string) - Hex string (throws if n is outside int64 range)
+ * new Int64(number) - Number (throws if n is outside int64 range)
+ * new Int64(hi, lo) - Raw bits as two 32-bit values
+ */
var Int64 = module.exports = function(a1, a2) {
if (a1 instanceof Buffer) {
this.buffer = a1;
@@ -57,9 +64,9 @@ Int64.MIN_INT = -Math.pow(2, 53);
Int64.prototype = {
/**
- * Do in-place 2's compliment. See
- * http://en.wikipedia.org/wiki/Two's_complement
- */
+ * Do in-place 2's compliment. See
+ * http://en.wikipedia.org/wiki/Two's_complement
+ */
_2scomp: function() {
var b = this.buffer, o = this.offset, carry = 1;
for (var i = o + 7; i >= o; i--) {
@@ -70,11 +77,12 @@ Int64.prototype = {
},
/**
- * Set the value:
- * setValue(string) - A hexidecimal string
- * setValue(number) - Number (throws if n is outside int64 range)
- * setValue(hi, lo) - Raw bits as two 32-bit values
- */
+ * Set the value. Takes any of the following arguments:
+ *
+ * setValue(string) - A hexidecimal string
+ * setValue(number) - Number (throws if n is outside int64 range)
+ * setValue(hi, lo) - Raw bits as two 32-bit values
+ */
setValue: function(hi, lo) {
var negate = false;
if (arguments.length == 1) {
@@ -85,21 +93,21 @@ Int64.prototype = {
hi = Math.abs(hi);
lo = hi % VAL32;
hi = hi / VAL32;
- if (hi > VAL32) throw RangeError(hi + ' is outside Int64 range');
+ if (hi > VAL32) throw new RangeError(hi + ' is outside Int64 range');
hi = hi | 0;
- } else if (typeof(hi) == 'string') {
+ } else if (typeof(hi) == 'string') {
hi = (hi + '').replace(/^0x/, '');
lo = hi.substr(-8);
hi = hi.length > 8 ? hi.substr(0, hi.length - 8) : '';
hi = parseInt(hi, 16);
lo = parseInt(lo, 16);
} else {
- throw Error(hi + ' must be a Number or String');
+ throw new Error(hi + ' must be a Number or String');
}
}
- // Technically we should throw if hi/lo is outside int32 range here, but
- // it's not worth the effort.
+ // Technically we should throw if hi or lo is outside int32 range here, but
+ // it's not worth the effort. Anything past the 32'nd bit is ignored.
// Copy bytes to buffer
var b = this.buffer, o = this.offset;
@@ -113,26 +121,36 @@ Int64.prototype = {
},
/**
- * Convert to a JS Number. Returns +/-Infinity for values that can't be
- * represented to integer precision.
- */
- valueOf: function() {
+ * Convert to a native JS number.
+ *
+ * WARNING: Do not expect this value to be accurate to integer precision for
+ * large (positive or negative) numbers!
+ *
+ * @param allowImprecise If true, no check is performed to verify the
+ * returned value is accurate to integer precision. If false, imprecise
+ * numbers (very large positive or negative numbers) will be forced to +/-
+ * Infinity.
+ */
+ toNumber: function(allowImprecise) {
var b = this.buffer, o = this.offset;
+ // Running sum of octets, doing a 2's complement
var negate = b[0] & 0x80, x = 0, carry = 1;
- for (var i = 0, ii = o + 7; i < 8; i++, ii--) {
- var v = b[ii];
- // Do a running 2's complement for negative numbers
+ for (var i = 7, m = 1; i >= 0; i--, m *= 256) {
+ var v = b[o+i];
+
+ // 2's complement for negative numbers
if (negate) {
v = (v ^ 0xff) + carry;
carry = v >> 8;
+ v = v & 0xff;
}
- x += (v & 0xff) * Math.pow(256, i);
+ x += v * m;
}
// Return Infinity if we've lost integer precision
- if (x >= Int64.MAX_INT) {
+ if (!allowImprecise && x >= Int64.MAX_INT) {
return negate ? -Infinity : Infinity;
}
@@ -140,15 +158,27 @@ Int64.prototype = {
},
/**
- * Return string value
- */
+ * Convert to a JS Number. Returns +/-Infinity for values that can't be
+ * represented to integer precision.
+ */
+ valueOf: function() {
+ return this.toNumber(false);
+ },
+
+ /**
+ * Return string value
+ *
+ * @param radix Just like Number#toString()'s radix
+ */
toString: function(radix) {
return this.valueOf().toString(radix || 10);
},
/**
- * Return a string showing the buffer octets, with MSB on the left.
- */
+ * Return a string showing the buffer octets, with MSB on the left.
+ *
+ * @param sep separator string. default is '' (empty string)
+ */
toOctetString: function(sep) {
var out = new Array(8);
var b = this.buffer, o = this.offset;
@@ -159,8 +189,8 @@ Int64.prototype = {
},
/**
- * Pretty output in console.log
- */
+ * Pretty output in console.log
+ */
inspect: function() {
return '[Int64 value:' + this + ' octets:' + this.toOctetString(' ') + ']';
}
View
2  package.json
@@ -8,5 +8,5 @@
"dependencies" : [],
"lib" : ".",
"main" : "./Int64.js",
- "version" : "0.2.0"
+ "version" : "0.3.0"
}
View
35 test.js
@@ -1,26 +1,31 @@
+var assert = require('assert');
var Int64 = require('./Int64');
var args = [
- [0], '0000000000000000',
- [1], '0000000000000001',
- [-1], 'ffffffffffffffff',
- [1e18], '0de0b6b3a7640000',
- ['0ff1234500654321'], '0ff1234500654321',
- [0xff12345, 0x654321], '0ff1234500654321',
- ['0x0000123450654321'], '0000123450654321',
- ['0xFFFFFFFFFFFFFFFF'], 'ffffffffffffffff'
+ [0], '0000000000000000', 0,
+ [1], '0000000000000001', 1,
+ [-1], 'ffffffffffffffff', -1,
+ [1e18], '0de0b6b3a7640000', 1e18,
+ ['0001234500654321'], '0001234500654321', 0x1234500654321,
+ ['0ff1234500654321'], '0ff1234500654321', 0xff1234500654300, // Imprecise!
+ [0xff12345, 0x654321], '0ff1234500654321', 0xff1234500654300, // Imprecise!
+ [0xfffaffff, 0xfffff700],'fffafffffffff700', -0x5000000000900,
+ [0xafffffff, 0xfffff700],'affffffffffff700', -0x5000000000000800, // Imprecise!
+ ['0x0000123450654321'], '0000123450654321', 0x123450654321,
+ ['0xFFFFFFFFFFFFFFFF'], 'ffffffffffffffff', -1
];
-for (var i = 0; i < args.length; i += 2) {
- var a = args[i], octets = args[i+1];
+// Test constructor argments
+
+for (var i = 0; i < args.length; i += 3) {
+ var a = args[i], octets = args[i+1], number = args[i+2];
+ console.log('Testing ' + a.join(', '));
// Create instance
var x = new Int64();
Int64.apply(x, a);
- console.log('new Int64(' + a + ')');
- var pass = x.toOctetString() == octets;
- console.log((pass ? 'PASS' : 'FAIL') + ' - value:' + x +
- ', octets: ' + x.toOctetString());
+ assert.equal(x.toOctetString(), octets,
+ 'Constuctor with ' + args.join(', '));
- console.log('-----------');
+ assert.equal(x.toNumber(true), number);
}
Please sign in to comment.
Something went wrong with that request. Please try again.