blob: 0d5f510722120d27aa857741814543bd0b7b4b39 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/**
* @fileoverview Definition of BigInt.
* @private
*/
var ByteUtil = require('./byte-util.js');
module.exports = BigInt;
/**
* @summary Represents an integer value of arbitrary size.
* @memberof module:vanadium.vdl
* @param {number} sign The sign of the number 1, -1 or 0.
* @param {Uint8Array} uintBytes The backing byte array, in network byte order.
* @constructor
*/
function BigInt(sign, uintBytes) {
this._sign = sign;
// Remove uppermost zero bytes.
this._bytes = new Uint8Array(trimBytes(uintBytes)); // Copy trimmed bytes.
Object.freeze(this);
if (sign === 0 && this._bytes.length !== 0) {
throw new Error('Sign is zero, but non-zero bytes \'' +
ByteUtil.bytes2Hex(this._bytes) + '\' passed to constructor.');
} else if (sign !== 0 && this._bytes.length === 0) {
throw new Error('Non-zero sign ' + sign +
', but zero bytes passed to constructor.');
} else if (sign !== 1 && sign !== -1 && sign !== 0) {
throw new Error('sign ' + sign + ' not supported.');
}
}
// Returns a subarray that excludes the uppermost zero bytes.
function trimBytes(bytes) {
var i = 0;
for (; i < bytes.length; i++) {
if (bytes[i] !== 0) {
break;
}
}
return bytes.subarray(i);
}
/**
* Create a BigInt from a native JavaScript number.
* @param {number} val A native JavaScript value.
* @throws {Error} If value cannot be represented as a BigInt.
* @return {modules:vdl.BigInt} The BigInt representation.
*/
BigInt.fromNativeNumber = function(val) {
if (typeof val !== 'number' || Math.round(val) !== val) {
throw new Error('fromNativeNumber can only convert integer values ' +
'(failing on ' + val + ')');
}
if (val > 9007199254740992 || val < -9007199254740992) {
throw new Error('Cannot convert 0x' + val.toString(16) + ' to big int. ' +
'Integers outside of (-2^53, 2^53)');
}
return convertFromNative(val);
};
/**
* Approximates a BigInt from a native JavaScript number.
* Caution: The conversion is not accurate for large numbers, non-integers, and
* non-numerical inputs.
* @param {number} val A native JavaScript value.
* @return {modules:vanadium.vdl.BigInt} The BigInt representation.
*/
BigInt.fromNativeNumberApprox = function(val) {
var floored;
if (typeof val !== 'number') {
floored = parseInt(val); // make an attempt to convert to an integer
} else {
floored = Math.floor(val);
}
return convertFromNative(floored);
};
// Converts from a number to a BigInt.
function convertFromNative(val) {
// The input is an integer, if it is 0, the BigInt is also 0.
if (val === 0) {
return new BigInt(0, new Uint8Array(0));
}
// Go through each 4-byte chunk of |val|.
var abs = Math.abs(val);
var chunks = [];
var CHUNK = 0x100000000;
do {
chunks.unshift(abs % CHUNK);
abs /= CHUNK;
} while (abs >= 1);
// Use these chunks to construct a Uint8Array for the BigInt.
var byteArr = new Uint8Array(4 * chunks.length);
var dataView = new DataView(byteArr.buffer);
for (var i = 0; i < chunks.length; i++) {
dataView.setUint32(4 * i, chunks[i], false);
}
return new BigInt(_sign(val), byteArr);
}
/**
* Generate a string representation of the BigInt.
* This must have the same output format as the string conversion of normal
* JavaScript integer (for the range of valid JavaScript integers).
* @return {string} The string representation of the BigInt.
*/
BigInt.prototype.toString = function() {
if (this._sign === 0) {
return '0';
}
var val = this;
var str = '';
if (this._sign === -1) {
val = this.negate();
str = '-';
}
var ten = new BigInt(1, new Uint8Array([10]));
var powerTen = new BigInt(1, new Uint8Array([0x01]));
while (val.greaterThan(powerTen)) {
powerTen = powerTen.multiply(ten);
}
// now powerTen >= val
var outputtedNonzeroVal = false;
while (powerTen._sign !== 0) {
var amt = val.divide(powerTen);
var nat = amt.toNativeNumber();
if (nat !== 0) {
str += nat.toString();
outputtedNonzeroVal = true;
} else if (outputtedNonzeroVal) {
str += '0';
}
var subtractOff = powerTen.multiply(amt);
val = val.subtract(subtractOff);
powerTen = powerTen.divide(ten);
}
return str;
};
/**
* Compares BigInt objects.
* @param {modules:vanadium.vdl.BigInt} other The BigInt to compare with this
* BigInt.
* @return {boolean} True if this BigInt is greater than the argument BigInt.
*/
BigInt.prototype.greaterThan = function(other) {
if (this._sign !== other._sign) {
return this._sign > other._sign;
}
if (this._sign === 0) {
return false;
}
if (this._bytes.length !== other._bytes.length) {
return ((this._bytes.length - other._bytes.length) * this._sign) > 0;
}
for (var i = 0; i < this._bytes.length; i++) {
if (this._bytes[i] > other._bytes[i]) {
return this._sign > 0;
}
if (other._bytes[i] > this._bytes[i]) {
return this._sign < 0;
}
}
return false;
};
/**
* Compares BigInt objects.
* @param {modules:vanadium.vdl.BigInt} other The BigInt to compare with this
* BigInt.
* @return {boolean} True if this BigInt is greater than or equal to
* the argument BigInt.
*/
BigInt.prototype.greaterThanEquals = function(other) {
return this.greaterThan(other) || this.equals(other);
};
/**
* Subtracts one BigInt from another.
* @param {modules:vanadium.vdl.BigInt} other The BigInt to subtract from this
* BigInt.
* @return {modules:vanadium.vdl.BigInt} Returns a new BigInt equal to this
* minus the argument BigInt.
*/
BigInt.prototype.subtract = function(other) {
if (this._sign === 0) {
return other.negate();
}
if (other._sign === 0) {
return this;
}
if (this._sign === 1 && other._sign === -1) {
return this.add(other.negate());
}
if (this._sign === -1 && other._sign === 1) {
return other.add(this.negate()).negate();
}
var firstGeq = this.greaterThanEquals(other);
var sign;
if (firstGeq) {
if (this.greaterThan(other)) {
sign = 1;
} else {
sign = 0;
}
} else {
sign = -1;
}
var greaterBytes = this._bytes;
var lessBytes = other._bytes;
if ((firstGeq && this._sign === -1) || (!firstGeq && this._sign === 1)) {
greaterBytes = other._bytes;
lessBytes = this._bytes;
}
var outArr = new Uint8Array(greaterBytes.length);
var carry = 0;
for (var place = 0; place < outArr.length; place++) {
var outArrIndex = outArr.length - place - 1;
var greaterIndex = greaterBytes.length - place - 1;
var lessIndex = lessBytes.length - place - 1;
var total = carry;
if (greaterIndex >= 0) {
total += greaterBytes[greaterIndex];
}
if (lessIndex >= 0) {
total -= lessBytes[lessIndex];
}
if (total < 0) {
carry = -1;
total += 256;
} else {
carry = 0;
}
outArr[outArrIndex] = total;
}
return new BigInt(sign, outArr);
};
/**
* Adds two BigInts together.
* @param {modules:vanadium.vdl.BigInt} other The BigInt to add to this BigInt.
* @return {modules:vanadium.vdl.BigInt} A new BigInt equal to this plus the
* argument BigInt.
*/
BigInt.prototype.add = function(other) {
if (this._sign === 0) {
return other;
}
if (other._sign === 0) {
return this;
}
if (this._sign === 1 && other._sign === -1) {
return this.subtract(other.negate());
}
if (this._sign === -1 && other._sign === 1) {
return other.subtract(this.negate());
}
var numBytesNeeded = Math.max(this._bytes.length, other._bytes.length);
var outArr = new Uint8Array(numBytesNeeded);
var carry = 0;
for (var place = 0; place < outArr.length; place++) {
var outArrIndex = outArr.length - place - 1;
var thisIndex = this._bytes.length - place - 1;
var otherIndex = other._bytes.length - place - 1;
var total = carry;
if (thisIndex >= 0) {
total += this._bytes[thisIndex];
}
if (otherIndex >= 0) {
total += other._bytes[otherIndex];
}
if (total >= 256) {
carry = 1;
total -= 256;
} else {
carry = 0;
}
outArr[outArrIndex] = total;
}
if (carry === 1) {
var newArr = new Uint8Array(numBytesNeeded + 1);
newArr.set(outArr, 1);
newArr[0] = 0x01;
outArr = newArr;
}
return new BigInt(this._sign, outArr);
};
/**
* Multiplies BigInts.
* @param {modules:vanadium.vdl.BigInt} other The BigInt to multiply with this
* BigInt.
* @return {modules:vanadium.vdl.BigInt} A new BigInt equal to this times the
* argument BigInt.
*/
BigInt.prototype.multiply = function(other) {
var total = new BigInt(0, new Uint8Array());
for (var b = 0; b < this._bytes.length; b++) {
var byteVal = this._bytes[b];
for (var i = 0; i < 8; i++) {
if ((byteVal & (1 << i)) !== 0) {
var bit = i + (this._bytes.length - b - 1) * 8;
var shiftedVal = other.leftShift(bit);
total = total.add(shiftedVal);
}
}
}
if (this._sign === -1) {
return total.negate();
}
return total;
};
/**
* Divides BigInts
* @param {modules:vanadium.vdl.BigInt} divisor The BigInt to use as the
* divisor.
* @return {modules:vanadium.vdl.BigInt} a new BigInt equalt to this divided by
* the argument BigInt.
*/
BigInt.prototype.divide = function(divisor) {
if (divisor._sign === 0) {
return NaN;
}
if (divisor.abs().greaterThan(this.abs())) {
return new BigInt(0, new Uint8Array());
}
var absDivisor = divisor.abs();
var result = new Uint8Array(this._bytes.length);
var remainder = new BigInt(0, new Uint8Array());
for (var i = 0; i < this._bytes.length; i++) {
for (var b = 7; b >= 0; b--) {
remainder = remainder.leftShift(1);
if ((this._bytes[i] & (1 << b)) !== 0) {
remainder = remainder.add(new BigInt(1, new Uint8Array([1])));
}
if (remainder.greaterThanEquals(absDivisor)) {
result[i] |= 1 << b;
remainder = remainder.subtract(absDivisor);
}
}
}
return new BigInt(this._sign * divisor._sign, result);
};
/**
* Negates the BigInt.
* @return {modules:vanadium.vdl.BigInt} A new BigInt that is a negated version
* this BigInt.
*/
BigInt.prototype.negate = function() {
return new BigInt(-this._sign, this._bytes);
};
/**
* Returns the absolute value of the BigInt.
* @return {modules:vanadium.vdl.BigInt} A new BigInt equal to the absolute
* value of this BigInt.
*/
BigInt.prototype.abs = function() {
return new BigInt(Math.abs(this._sign), this._bytes);
};
function mostSignificantBitForByte(b) {
var count = 0;
if (b >= 0x10) {
count += 4;
b >>= 4;
}
if (b >= 0x04) {
count += 2;
b >>= 2;
}
if (b >= 0x02) {
count += 1;
}
return count;
}
/**
* Performs left shift of an arbitrary amount.
* @param {number} amt The amount to shift in bits.
* @return {modules:vanadium.vdl.BigInt} A new BigInt that is left shifted by
* the specified amount.
*/
BigInt.prototype.leftShift = function(amt) {
if (this._bytes.length === 0) {
return this;
}
var spaceRemaining = 7 - mostSignificantBitForByte(this._bytes[0]);
var extraSpaceNeeded = Math.ceil((amt - spaceRemaining) / 8);
var spaceNeeded = extraSpaceNeeded + this._bytes.length;
var result = new Uint8Array(spaceNeeded);
var bitOffset = amt % 8;
if (bitOffset === 0) {
result.set(this._bytes);
} else {
var highLeftShift = bitOffset;
var highMask = (1 << (8 - bitOffset)) - 1;
var lowRightShift = 8 - bitOffset;
var extraOffset = 0;
if ((this._bytes[0] >> lowRightShift) > 0) {
extraOffset = 1;
}
for (var i = 0; i < this._bytes.length; i++) {
var b = this._bytes[i];
if (i + extraOffset > 0) {
result[i + extraOffset - 1] |= b >> lowRightShift;
}
result[i + extraOffset] |= ((b & highMask) << highLeftShift);
}
}
return new BigInt(this._sign, result);
};
/**
* Determine if this BigInt is equal to another BigInt.
*
* @param {modules:vanadium.vdl.BigInt} other The other BigInt to compare.
* @return {boolean} true if this BigInt is equal to the other BigInt. false
* otherwise.
*/
BigInt.prototype.equals = function(other) {
if (this.getSign() !== other.getSign()) {
return false;
}
var thisBytes = this.getUintBytes();
var otherBytes = other.getUintBytes();
if (thisBytes.length !== otherBytes.length) {
return false;
}
for (var i = 0; i < thisBytes.length; i++) {
if (thisBytes[i] !== otherBytes[i]) {
return false;
}
}
return true;
};
/**
* Gets the sign of this BigInt.
* @return {sign} 1 if positive, 0 if zero, -1 if negative.
*/
BigInt.prototype.getSign = function() {
return this._sign;
};
/**
* Gets the uint byte value of this big int.
* This method trims upper zero bytes.
* @return {Uint8Array} The uint bytes.
*/
BigInt.prototype.getUintBytes = function() {
return this._bytes;
};
/**
* Convert to a native JavaScript float64 representation.
* @throws {Error} if the value cannot be converted to a float64 without loss.
* @return {number} a native JavaScript float64 representation of the BigInt.
*/
BigInt.prototype.toNativeNumber = function() {
if (this._largerThanMaxLosslessInteger()) {
throw new Error('BigInt \'' + ByteUtil.bytes2Hex(this) +
'\' out of range of native JavaScript numbers');
}
return this._convertToNative();
};
/**
* Approximate the native JavaScript float64 representation.
* Caution: The conversion is not accurate when the BigInt is larger than the
* maximum lossless integer.
* @return {number} a native JavaScript float64 representation of the BigInt.
*/
BigInt.prototype.toNativeNumberApprox = function() {
return this._convertToNative();
};
/**
* @private
* @return {number} a native JavaScript float64 representation of the BigInt.
*/
BigInt.prototype._convertToNative = function() {
var arr = new Uint8Array(4);
var copySrcIndex = this._bytes.length - Math.min(this._bytes.length, 4);
var copyDstIndex = Math.max(4 - this._bytes.length, 0);
arr.set(this._bytes.subarray(copySrcIndex), copyDstIndex);
var view = new DataView(arr.buffer);
var lowerVal = view.getUint32(0, false);
if (this._bytes.length <= 4) {
return this._sign * lowerVal;
}
copySrcIndex = this._bytes.length - Math.min(this._bytes.length, 8);
copyDstIndex = Math.max(8 - this._bytes.length, 0);
var copyableLength = Math.min(this._bytes.length - 4, 4);
var arr2 = new Uint8Array(4);
arr2.set(this._bytes.subarray(copySrcIndex, copySrcIndex + copyableLength),
copyDstIndex);
var view2 = new DataView(arr2.buffer);
var upperVal = view2.getUint32(0, false);
var combinedVal = upperVal * 0x100000000 + lowerVal;
return this._sign * combinedVal;
};
/**
* @private
* @return true if abs(this) > 2^53, false otherwise.
*/
BigInt.prototype._largerThanMaxLosslessInteger = function() {
if (this._bytes.length >= 8) {
return true;
}
if (this._bytes.length <= 6) {
return false;
}
if (this._bytes[0] > 0x20) {
return true;
}
if (this._bytes[0] === 0x20) {
for (var i = 1; i <= 6; i++) {
if (this._bytes[i] !== 0) {
return true;
}
}
}
return false;
};
/**
* Get the sign of the value.
* @private
* @param {number} val Input value.
* @return {number} 1, -1, or 0 depending on the sign of the input.
*/
function _sign(val) {
if (val > 0) {
return 1;
} else if (val < 0) {
return -1;
}
return 0;
}