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

fix: BigInt exponentiation transpiler error (@dfinity/candid) #599

Merged
merged 3 commits into from Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/candid/src/idl.ts
Expand Up @@ -15,6 +15,7 @@ import {
writeIntLE,
writeUIntLE,
} from './utils/leb128';
import { iexp2 } from './utils/bigint-math';

// tslint:disable:max-line-length
/**
Expand Down Expand Up @@ -630,8 +631,8 @@ export class FixedIntClass extends PrimitiveType<bigint | number> {
}

public covariant(x: any): x is bigint {
const min = BigInt(2) ** BigInt(this._bits - 1) * BigInt(-1);
const max = BigInt(2) ** BigInt(this._bits - 1) - BigInt(1);
const min = iexp2(this._bits - 1) * BigInt(-1);
const max = iexp2(this._bits - 1) - BigInt(1);
if (typeof x === 'bigint') {
return x >= min && x <= max;
} else if (Number.isInteger(x)) {
Expand Down Expand Up @@ -683,7 +684,7 @@ export class FixedNatClass extends PrimitiveType<bigint | number> {
}

public covariant(x: any): x is bigint {
const max = BigInt(2) ** BigInt(this._bits);
const max = iexp2(this._bits);
if (typeof x === 'bigint' && x >= BigInt(0)) {
return x < max;
} else if (Number.isInteger(x) && x >= 0) {
Expand Down
30 changes: 30 additions & 0 deletions packages/candid/src/utils/bigint-math.test.ts
@@ -0,0 +1,30 @@
import { ilog2, iexp2 } from './bigint-math';

test('ilog2', () => {
for (let n = 1; n < 100; n++) {
expect(ilog2(n)).toBe(n > 0 ? Math.floor(Math.log2(n)) : NaN);
}
expect(() => ilog2(0)).toThrow('Input must be positive');
expect(() => ilog2(-1)).toThrow('Input must be positive');
expect(() => ilog2(1.5)).toThrow(
'The number 1.5 cannot be converted to a BigInt because it is not an integer',
);
expect(() => (ilog2 as (any) => number)('abc')).toThrow('Cannot convert abc to a BigInt');
});

test('iexp2', () => {
for (let n = 0; n < 10; n++) {
expect(iexp2(n)).toBe(BigInt(2 ** n));
}
expect(() => ilog2(-1)).toThrow('Input must be positive');
expect(() => iexp2(1.5)).toThrow(
'The number 1.5 cannot be converted to a BigInt because it is not an integer',
);
expect(() => (ilog2 as (any) => number)('abc')).toThrow('Cannot convert abc to a BigInt');
});

test('ilog2 and iexp2', () => {
for (const p of [0, 1, 3, 55, 10000]) {
expect(ilog2(iexp2(BigInt(p)))).toBe(p);
}
});
28 changes: 28 additions & 0 deletions packages/candid/src/utils/bigint-math.ts
@@ -0,0 +1,28 @@
/**
* Equivalent to `Math.log2(n)` with support for `BigInt` values
*
* @param n bigint or integer
* @returns integer
*/
export function ilog2(n: bigint | number): number {
const nBig = BigInt(n);
if (n <= 0) {
throw new RangeError('Input must be positive');
}
return nBig.toString(2).length - 1;
}

/**
* Equivalent to `2 ** n` with support for `BigInt` values
* (necessary for browser preprocessors which replace the `**` operator with `Math.pow`)
*
* @param n bigint or integer
* @returns bigint
*/
export function iexp2(n: bigint | number): bigint {
const nBig = BigInt(n);
if (n < 0) {
throw new RangeError('Input must be non-negative');
}
return BigInt(1) << nBig;
}