Int64 and Uint64 Objects

Int64 and Uint64 are object classes for handling 64-bit integer values. These are included primarily to support interaction with native code through the DLL importer, but you can use them for general purposes in Javascript as well. The reason special classes are provided for these types is that many Windows APIs use 64-bit integers, but Javascript doesn't have its own primitive type or built-in object class that's capable of representing this kind of value.

There are two kinds of 64-bit integers: signed and unsigned. Int64 is the signed type, and Uint64 is the unsigned type. (The "U" is for "unsigned", as you undoubtedly already guessed. The naming and capitalization were chosen to parallel the names of the built-in Javascript Typed Array types, which use names like Int8Array, Uint8Array, etc.)

"Signed" means that the type has a positive/negative sign as part of its value, so it can store both positive and negative numbers. "Unsigned" means that there's no such sign information, so only positive (or zero) values can be stored. Why would you want the unsigned kind? For one thing, some values are just inherently non-negative, such as a length or weight. The more practical reason, though, is that the unsigned version of an integer type has twice as high an upper limit to the numbers it can store compared to the signed version, and the extra capacity is useful in some cases.

The signed type, Int64, can hold values from -9223372036854775808 to 9223372036854775807.

The unsigned type, Uint64, can hold values from 0 to 18446744073709551615.

Why do we need these?

Because some Windows APIs use 64-bit integer values, and because Javascript can't adequately represent these values with any of its native types.

The closest thing Javascript has is its standard Number type. A Javascript Number is represented as a "double precision floating point" value. That's a standard format, the same one used in C/C++ for the "double" type. A double is actually a 64-bit type itself, but the 64 bits are divided up into several subfields, in such a way that you can only store 53 bits worth of "integer" information. So if you have a 64-bit integer value that's outside of the 53-bit range - that is, -9007199254740991 to +9007199254740991 - you can't precisely represent it as a Javascript number. A Javascript number can only approximate it, with some of the digits at the end lost. In contrast, a 64-bit integer can exactly represent every integer value over the wider ranges mentioned above.

There's a weird paradox about converting between Javascript numbers and 64-bit integers: you can lose information doing a conversion in either direction. Neither type is a full superset of the other. Javascript numbers have an "exponent" component (one of those bit subfields) that lets them represent very large and very small numbers, so they can be used to store values that are outside of the magnitude range of a 64-bit integer. But as described above, a 64-bit integer can store values with exact precision over a wider range. So each type can represent certain values that the other can't.

Creation

You can create a 64-bit integer value with these classes using new Int64(value) and new Uint64(value). The value can be a number or a string representing a number.

let b = new Int64(1000000); let a = new Int64("0x12345678ABCDEF");

If you use a string, it's treated as a decimal (base 10) number by default. You can specify a different radix as follows:

If you pass a Javascript number as the argument, and the number is outside of the range that can be represented by the 64-bit integer type, an error is thrown.

Alternatively, you can pass in an object with high and low properties representing the high-order 32 bits and low-order 32 bits of the value, respectively. This is the same format that the toObject() method returns, so you can use it to convert that format back into a proper 64-bit integer value.

let c = new Int64({high: 0x1234, low: 0x5678ABCD}); // same as new Int64("0x12345678ABCD")

Arithmetic

Int64 and Uint64 values are objects, so Javascript won't let us perform arithmetic on them using the normal algebraic symbols (+, -, *, /). To make up for this, the objects provide methods for performing the basic math operations. Some examples:

let a = new Int64('1000000000000'); let b = new Int64('2000000000000'); let c = a.add(b); // c = a + b; a and b are unchanged let d = a.mul(1000); // you can use ordinary Javascript numbers as arguments, too let e = a.sub('999'); // other types, like strings, are converted to Int64 implicitly

The comparison operators (>, <, ==, etc) don't work arithmetically on these values, either, for the same reason: the comparison operators treat these values as Javascript objects, not numbers. For arithmetic comparisons, use the compare() method:

if (a.compare(b) > 0) ... // true if a > b arithmetically if (a.compare(b) < 0) ... // true if a < b if (a.compare(b) == 0) ... // true if a == b

All of the math operations return new Int64 or Uint64 objects representing the results. The original value is never affected. Like a Javascript string, the value contained in an Int64/Uint64 is immutable.

The math methods accept Int64, Uint64, number, or string values as arguments. Strings are parsed as numbers of the same type as the "this" object of the method.

b = a.add(100).mul("2"); // add 100, then multiply by 2

Here's a quick reference to the basic arithmetic operators and their Int64/Uint64 method equivalents:

OperationMethod
-aa.negate()
a + ba.add(b)
a - ba.sub(b)
a * ba.mul(b)
a / ba.div(b)
a % ba.mod(b)
~aa.not()
a & ba.and(b)
a | ba.or(b)
a << ba.shl(b)
a >> ba.ashr(b)
a >>> ba.lshr(b)
a < ba.compare(b) < 0
a <= ba.compare(b) <= 0
a > ba.compare(b) > 0
a >= ba.compare(b) >= 0
a == ba.compare(b) == 0
a != ba.compare(b) != 0

Methods

add(value): Adds value to the object's value and returns a new Int64 or Uint64 object representing the result. value can be specified as a Javascript number or a string; in either case, the value is converted to an Int64/Uint64 according to the same rules used in "new" (including an error if the value is too large for the 64-bit integer type). No error is thrown if the result of the addition overflows; the result simply truncates overflow bits, the same as normal native integer arithmetic.

and(value): Performs a bit-wise AND operation on this object's value and value, and returns a new Int64 or Uint64 representing the result.

ashr(bits): Performs the Javascript >> or "arithmetic right shift" operation. Bit-shifts the object's value right by bits bits, retaining its sign, and returns a new Int64 or Uint64 object representing the result. The "arithmetic" right-shift operation differs from the "logical" right-shift in that the arithmetic version retains the sign (positive or negative) of the original value. This has the effect of dividing the value by 2bits, discarding any fractional part of the result (rounding towards zero).

compare(value): Compares the object's value to value, returning -1 if the "this" object is less than value, 0 if the values are equal, 1 if "this" is greater than value. The return value is a regular Javascript number, not another Int64 value. When comparing signed to unsigned, the comparison is done so that it yields the mathematically correct result.

div(value): Divides the object's value by value returns a new Int64 or Uint64 object representing the result. The division is done as an integer quantity, so any fractional part is discarded without rounding.

lshr(bits): Performs the Javascript >>> or "logical right shift" operation. Bit-shifts the object's value right by bits bits, discarding its sign, and returns a new Int64 or Uint64 object representing the result. The "logical" right-shift operation differs from the "arithmetic" right-shift in that the logical version discards the sign of the original value; the high bits are filled with zeros after the shift. This is roughly the same as dividing the value by 2bits, except that its effect on negative values is different from a regular division because of the way the sign bit is always zeroed.

mod(value): Divides the object's value by value and returns a new Int64 or Uint64 representing the "modulo" value, or integer remainder. This uses the hardware platform rules for negative values.

mul(value): Multiplies the object's value by value, and returns a new Int64 or Uint64 object representing the result. No error is thrown on overflow; the result simply truncates overflow bits, the same as normal native integer arithmetic.

negate(): Returns a new Int64/Uint64 object containing the negative of the object's value.

not(): Returns a new Int64/Uint64 object containing the binary complement of object's value's bits.

or(value): Performs a bit-wise OR operation on this object's value and value, and returns a new Int64 or Uint64 representing the result.

shl(bits): Performs the Javascript << or "left shift" operation. This bit-shifts the object's value left by bits bits, returning a new Int64 or Uint64 object representing the result. This has the effect of multiplying the value by 2bits.

sub(value): Subtracts value from the object's value and returns a new Int64 or Uint64 object representing the result. No error is thrown on overflow; the result simply truncates overflow bits, the same as normal native integer arithmetic.

toNumber(): Returns the value represented as an ordinary Javascript number. Javascript numbers can only store integer values accurately from -9007199254740991 to 9007199254740991, which is smaller than the 64-bit integer range (technically, the Javascript numeric representation provides the equivalent of a 53-bit integer range). If the value in the Int64 or Uint64 is outside of this range, toNumber() throws an error.

toObject(): Returns an object with two properties, high and low, containing Javascript number values giving the high-order 32 bits and low-order 32-bits of the 64-bit value. For example:

let l = new Int64("0x12345678abcdef99"); let o = l.toObject(); message("o.high=" + o.high.toString(16) + ", o.low=" + o.low.toString(16)); // displays: o.high=12345678, o.low=abcdef99

For a signed (Int64) value, the high part has the same sign as the 64-bit value. The low part is unsigned regardless of the sign of 64-bit value, so it's always positive.

o = l.toObject(new Int64(-1)); message("o.high=" + o.high + ", o.low=" + o.low); // displays: o.high=-1, o.low=4294967295

The point of the object format is that it lets you do arithmetic in the Javascript domain, by decomposing the large number into two smaller values that fit in the regular Javascript numeric type. You can use this for any operations that you can't express conveniently in terms of the math methods that Int64 provides. You can convert the result back to a proper 64-bit integer object by passing it as the argument to new Int64() or new Uint64().

toString(radix): Converts the value to a string representation. The optional radix is a number from 2 to 36 specifying the radix (base) for the string version; the default is decimal (base 10).