Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement power function (clean up) #76

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ API
* Long#**divide**/**div**(divisor: `Long | number | string`): `Long`<br />
Returns this Long divided by the specified.

* Long#**clone**(): `Long`<br />
Returns a deep copy of this Long.

* Long#**power**/**pow**(exp: `Long | number`): `Long`<br />
Returns this Long to given integer power.

* Long#**equals**/**eq**(other: `Long | number | string`): `boolean`<br />
Tests if this Long's value equals the specified's.

Expand Down
2 changes: 1 addition & 1 deletion dist/long.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/long.js.map

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions externs/long.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,23 @@ Long.prototype.divide = function(other) {};
*/
Long.prototype.div = function(other) {};

/**
* @return {!Long}
*/
Long.prototype.clone = function() {};

/**
* @param {!Long|number} other
* @return {!Long}
*/
Long.prototype.power = function(other) {};

/**
* @param {!Long|number} other
* @return {!Long}
*/
Long.prototype.pow = function(other) {};

/**
* @param {!Long|number|string} other
* @return {!Long}
Expand Down
15 changes: 15 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@ declare class Long {
*/
div(divisor: Long | number | string): Long;

/**
* Returns a deep copy of this Long.
*/
clone(): Long;

/**
* Returns this Long to given integer power.
*/
power(exp: Long | number): Long;

/**
* Returns this Long to given integer power.
*/
pow(exp: Long | number): Long;

/**
* Tests if this Long's value equals the specified's.
*/
Expand Down
98 changes: 94 additions & 4 deletions src/long.js
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ LongPrototype.sub = LongPrototype.subtract;
*/
LongPrototype.multiply = function multiply(multiplier) {
if (this.isZero())
return ZERO;
return this;
if (!isLong(multiplier))
multiplier = fromValue(multiplier);

Expand All @@ -877,11 +877,11 @@ LongPrototype.multiply = function multiply(multiplier) {
}

if (multiplier.isZero())
return ZERO;
return (this.unsigned?UZERO:ZERO);
if (this.eq(MIN_VALUE))
return multiplier.isOdd() ? MIN_VALUE : ZERO;
return multiplier.isOdd() ? MIN_VALUE : (this.unsigned?UZERO:ZERO);
if (multiplier.eq(MIN_VALUE))
return this.isOdd() ? MIN_VALUE : ZERO;
return this.isOdd() ? MIN_VALUE : (this.unsigned?UZERO:ZERO);

if (this.isNegative()) {
if (multiplier.isNegative())
Expand Down Expand Up @@ -1086,6 +1086,96 @@ LongPrototype.modulo = function modulo(divisor) {
return this.sub(this.div(divisor).mul(divisor));
};

/**
* Returns a deep copy of this Long.
* @this {!Long}
* @returns {!Long} Deep copy of this Long
*/
LongPrototype.clone = function clone() {
return new Long(this.low, this.high, this.unsigned);
};

/* Index is exponent-2, value is maximum base that fits in 64 bits */
var bases_u64 = [
4294967295,2642245,65535,7131,1625,565,
255,138,84,56,40,30,23,19,
15,13,11,10,9,8,7,6,
6,5,5,5,4,4,4,4,
3,3,3,3,3,3,3,3,
3,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2
];
/* Index is exponent-2, value is maximum base that fits in 63 bits */
var bases_s64 = [
3037000499,2097151,55108,6208,1448,511,
234,127,78,52,38,28,22,18,
15,13,11,9,8,7,7,6,
6,5,5,5,4,4,4,4,
3,3,3,3,3,3,3,3,
2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,1
];
/* Index is exponent-2, value is minimum base that fits in 63 bits */
var bases_s64n = [
-3037000499,-2097152,-55108,-6208,-1448,-512,
-234,-128,-78,-52,-38,-28,-22,-18,
-15,-13,-11,-9,-8,-8,-7,-6,
-6,-5,-5,-5,-4,-4,-4,-4,
-3,-3,-3,-3,-3,-3,-3,-3,
-2,-2,-2,-2,-2,-2,-2,-2,
-2,-2,-2,-2,-2,-2,-2,-2,
-2,-2,-2,-2,-2,-2,-2,-2
];
/* Determine if b ** e will over/under flow (b=Long,e=number) */
function isPowerOverflow(b,e)
{
// Each look up table has 62 items, making 63 the max exp (index=exp-2). Anything else including floats are undefined.
if (b.isNegative()) {
var m = bases_s64n[e-2];
if (m==undefined || b.lt(m)) return true;
}
else {
var m = (b.unsigned?bases_u64:bases_s64)[e-2];
if (m==undefined || b.gt(m)) return true;
}
return false;
}

/**
* Returns this Long to given integer power.
* @this {!Long}
* @param {!Long|number} exp Integer power
* @returns {!Long} This Long to given integer power
*/
LongPrototype.power = function power(exp) {
var a = this;
if (a.eq(Long.ONE)) return a;
if (isLong(exp)) exp = (exp.gt(63) ? 64 : exp.toInt()); // if long is >63, make sure it stays that way until the overflow check catches it
if (exp===0) return (a.unsigned?UONE:ONE); // zero to zero is treated as one by many languages
if (a.isZero()) {
if (exp < 0) throw Error('Zero to negative power is undefined'); // or return Infinity?
return a;
}
if (exp===1) return a;
if (exp < 0) return (a.unsigned?UZERO:ZERO); // Long.ONE.div(a.pow(-exp)); // being only integers, this will probably always be zero via truncation?
if (isPowerOverflow(a,exp)) throw Error('Overflow: ' + a.toString() + ' ** ' + exp);
while ((exp & 1)===0) {
exp >>>= 1;
a = a.mul(a);
}
return (exp===1 ? a : a.pow(exp-1).mul(a));
};

/**
* Returns this Long to given integer power. This is an alias of {@link Long#power}.
* @function
* @param {!Long|number} exp Power
* @returns {!Long} This Long to given integer power
*/
LongPrototype.pow = LongPrototype.power;

/**
* Returns this Long modulo the specified. This is an alias of {@link Long#modulo}.
* @function
Expand Down
94 changes: 94 additions & 0 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,100 @@ function testUnsignedMsbUnsigned() {
assert.strictEqual(Long.fromString("9223372036854775808", true).toString(), "9223372036854775808");
},

function testPower() {
var a = Long.fromNumber(11);
var b = a.power(12);
var res = Long.fromNumber(3138428376721);
var err;
assert.deepEqual(b, res);
// check negative, which will probably always be zero since we can't store fractional result?
b = a.power(-3);
assert.deepEqual(b, Long.ZERO);
// except perhaps one which has a short circuit condition anyway?
b = Long.ONE.power(-1);
assert.deepEqual(b, Long.ONE);
b = Long.UONE.power(-1);
//if (!b.eq(Long.ONE)) throw new Error('Power failed to compute UONE to -1');
assert.deepEqual(b, Long.UONE);
// negative number
a = Long.fromNumber(-11);
b = a.power(11);
res = Long.fromNumber(-285311670611);
assert.deepEqual(b, res);
// check as unsigned
a = Long.fromNumber(11, true);
b = a.power(12);
res = Long.fromNumber(3138428376721, true);
assert.deepEqual(b, res);
// zero base
b = Long.ZERO.power(123);
assert.deepEqual(b, Long.ZERO);
// one base
b = Long.ONE.power(123);
assert.deepEqual(b, Long.ONE);
// zero power
a = Long.fromNumber(123);
b = a.power(Long.ZERO);
assert.deepEqual(b, Long.ONE);
// one power
b = a.power(Long.ONE);
assert.deepEqual(b, a);
// zero to zero power
b = Long.ZERO.power(Long.ZERO);
assert.deepEqual(b, Long.ONE);
// zero to negative power
err = null;
try { b = Long.ZERO.power(-3); }
catch (e) { err = e; }
if (err==null) throw new Error('Zero to negative power should throw error');
// overflow edge case - 8 is max base for a power of 20
a = Long.fromNumber(8);
b = a.power(20);
assert.deepEqual(b, Long.fromString('1152921504606846976'));
// overflow edge case - -8 is max base for a power of 20
a = Long.fromNumber(-8);
b = a.power(20);
assert.deepEqual(b, Long.fromString('1152921504606846976'));
// overflow negative edge case
a = Long.fromNumber(-2097152);
b = a.power(3);
assert.deepEqual(b, Long.MIN_VALUE);
// overflow negative edge case
a = Long.fromNumber(-2);
b = a.power(63);
assert.deepEqual(b, Long.MIN_VALUE);
// overflow error - 64bit bound
err = null;
a = Long.fromNumber(2, true);
try { b = a.power(Long.fromNumber(64)); }
catch (e) { err = e; }
if (err==null) throw new Error('2 ** 64 should throw overflow error (as max is 2**64-1)');
// overflow error - arbitrary number
err = null;
a = Long.fromNumber(33);
try { b = a.power(Long.fromNumber(19)); }
catch (e) { err = e; }
if (err==null) throw new Error('33 ** 19 should throw overflow error');
// overflow error negative case
err = null;
a = Long.fromNumber(-2097153);
try { b = a.power(3); }
catch (e) { err = e; }
if (err==null) throw new Error('-2097153 ** 3 should throw overflow error');
// overflow error negative case with even power
err = null;
a = Long.fromNumber(-2097153);
try { b = a.power(4); }
catch (e) { err = e; }
if (err==null) throw new Error('-2097153 ** 4 should throw overflow error');
// error on float power
err = null;
a = Long.fromNumber(2);
try { b = a.power(4.5); }
catch (e) { err = e; }
if (err==null) throw new Error('2 ** 4.5 should throw error');
},

function testIssue31() {
var a = new Long(0, 8, true);
var b = Long.fromNumber(2656901066, true);
Expand Down