MikeMcl / bignumber.js

A JavaScript library for arbitrary-precision decimal and non-decimal arithmetic
http://mikemcl.github.io/bignumber.js
MIT License
6.64k stars 741 forks source link

Cannot create a Big Number #291

Closed aress31 closed 3 years ago

aress31 commented 3 years ago

I am trying to create a BN object from the value 10**32, see the following test:

  it("has a quadrilllion (10^15) initial total supply", async () => {
    const decimals = await this.token.decimals();
    const totalSupply = await this.token.totalSupply();
    // console.log(totalSupply);
    console.log(new BN(10 ** 32));
    // console.log((10 ** 15 * 10 ** decimals.toNumber()).toString());
    // console.log(totalSupply, expected);
    // expect(await this.token.totalSupply()).to.be.bignumber.equal(
    //   (10 ** 15 * 10 ** decimals.toNumber()).toString()
    // );
  });

But it seems that the library cannot handle this Big Number, see the following stack trace:

  1) Contract: Initial State
       has a quadrilllion (10^15) initial total supply:
     Error: Assertion failed
      at assert (node_modules\bn.js\lib\bn.js:6:21)
      at BN._initNumber (node_modules\bn.js\lib\bn.js:128:7)
      at BN.init [as _init] (node_modules\bn.js\lib\bn.js:82:19)
      at new BN (node_modules\bn.js\lib\bn.js:39:12)
      at Context.<anonymous> (test\ERC20Deflationary.test.js:35:17)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)

I also tried to initialise the Big Number with a string instead but the value is totally off, see:

  it("has a quadrilllion (10^15) initial total supply", async () => {
    const decimals = await this.token.decimals();
    const totalSupply = await this.token.totalSupply();
    // console.log(totalSupply);
    console.log(new BN((10 ** 32).toString()));
    // console.log((10 ** 15 * 10 ** decimals.toNumber()).toString());
    // console.log(totalSupply, expected);
    // expect(await this.token.totalSupply()).to.be.bignumber.equal(
    //   (10 ** 15 * 10 ** decimals.toNumber()).toString()
    // );
  });

  Contract: Initial State
    √ the deployer is the owner (104ms)
    √ has a name (124ms)
    √ has a symbol (124ms)
    √ has 18 decimals (87ms)
BN { negative: 0, words: [ 23532 ], length: 1, red: null }
    √ has a quadrilllion (10^15) initial total supply (173ms)
    1) assigns the initial total supply to the owner
    > No events were emitted

Could anyone please help?

shuckster commented 3 years ago
console.log('1', 10 ** 32)
console.log('2', new BigNumber(10 ** 32).toString())
console.log('3', new BigNumber(10).exponentiatedBy(32).toString())
// 1 1e+32
// 2 1e+32
// 3 1e+32

Seems fine.

Are you sure you're interpreting your test correctly?

  Contract: Initial State
   (...cut...)
    √ has a quadrilllion (10^15) initial total supply (173ms) <-- PASS
    1) assigns the initial total supply to the owner <-- FAIL
    > No events were emitted

The it you posted does not have the description "assigns the initial total supply to the owner"?

aress31 commented 3 years ago

@shuckster thanks for trying to help, maybe this output will be more ufsefull:

  it("has a quadrilllion (10^15) initial total supply", async () => {
    const decimals = await this.token.decimals();
    const totalSupply = await this.token.totalSupply();
    console.log(totalSupply.toString());
    console.log("1", 10 ** 32);
    console.log("2", BN(10 ** 32).toString());
    console.log("3", BN(10).exponentiatedBy(32).toString());
    // console.log(new BN(expected));
    // console.log((10 ** 15 * 10 ** decimals.toNumber()).toString());
    // console.log(totalSupply, expected);
    // expect(await this.token.totalSupply()).to.be.bignumber.equal(
    //   (10 ** 15 * 10 ** decimals.toNumber()).toString()
    // );
  });
  Contract: Initial State
    √ the deployer is the owner (76ms)
    √ has a name (110ms)
    √ has a symbol (108ms)
    √ has 18 decimals (98ms)
1000000000000000000000000000000000
1 1e+32
    1) has a quadrilllion (10^15) initial total supply
    > No events were emitted

  4 passing (1s)
  1 failing

  1) Contract: Initial State
       has a quadrilllion (10^15) initial total supply:
     TypeError: Cannot set property 'negative' of undefined
      at BN (node_modules\bn.js\lib\bn.js:26:19)
      at Context.<anonymous> (test\ERC20Deflationary.test.js:36:22)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)

Btw I am using BN from @openzeppelin/test-helpers not sure if it is based on this lib.

EDIT: Alright it is based on:

require('../configure')();

const web3 = require('./config/web3').getWeb3();

const BN = web3.utils.BN;
const chaiBN = require('chai-bn')(BN);

require('chai').use(chaiBN);

// Installing chai-bn for the user is part of the offering.
//
// The chai module used internally by us may not be the same one that the user
// has in their own tests. This can happen if the version ranges required don't
// intersect, or if the package manager doesn't dedupe the modules for any
// other reason. We do our best to install chai-bn for the user.
for (const mod of [require.main, module.parent]) {
  try {
    mod.require('chai').use(chaiBN);
  } catch (e) {
    // Ignore errors
  }
}

module.exports = {
  web3,
  BN,
};

EDIT 1: I switched to this library instead and it handles exponential 32 fine, however , the default BN format returned by web3 (see totalSupply) apperars to be different from the one used in this lib, therefore, I am not sure if my expect see this output:

  it("has a quadrilllion (10^15) initial total supply", async () => {
    const decimals = await this.token.decimals();
    const totalSupply = await this.token.totalSupply();
    console.log("totalSupply", totalSupply);
    console.log("1", 10 ** 32);
    console.log("2",  new BigNumber(10 ** 32));
    console.log("3",  new BigNumber(10).exponentiatedBy(32));
    console.log("4",  new BigNumber(10).exponentiatedBy(15 + decimals));
    // console.log(new BN(expected));
    // console.log((10 ** 15 * 10 ** decimals.toNumber()).toString());
    // console.log(totalSupply, expected);
    // expect(await this.token.totalSupply()).to.be.bignumber.equal(
    //   (10 ** 15 * 10 ** decimals.toNumber()).toString()
    // );
  });
  Contract: Initial State
    √ the deployer is the owner (125ms)
    √ has a name (89ms)
    √ has a symbol (140ms)
    √ has 18 decimals (73ms)
totalSupply BN {
  negative: 0,
  words: [ 0, 5685888, 14234508, 20388114, 49, <1 empty item> ],
  length: 5,
  red: null
}
1 1e+32
2 BigNumber { s: 1, e: 32, c: [ 10000 ] }
3 BigNumber { s: 1, e: 32, c: [ 10000 ] }
4 BigNumber { s: 1, e: 1518, c: [ 1000000 ] }
    √ has a quadrilllion (10^15) initial total supply (258ms)

Does anyone know what I should do?

shuckster commented 3 years ago

I think you're asking for help in the wrong place.

Despite being based on bignumber.js, the error you're getting seems more to do with the fork.

Maybe ask bn.js for help?

aress31 commented 3 years ago

I managed to get it work this way:

    expect(await this.token.totalSupply()).to.be.bignumber.equal(
      new BigNumber(10).exponentiatedBy(33).toFixed()
    );

Just wondering how I could do something like that:

const decimals = await this.token.decimals();
console.log(new BigNumber(10).exponentiatedBy(15 + decimals));
shuckster commented 3 years ago

Is this what you're looking for?

new BigNumber(10).exponentiatedBy(15).toFormat(2)
aress31 commented 3 years ago

No decimals is 15 basically I want to sum 15 + 18 since decimals is variable and my test depends on this value.

shuckster commented 3 years ago

If you want the whole expression to be BigNumber you could do this:

BigNumber(10).exponentiatedBy(BigNumber.sum(15, decimals))

But I don't think it's necessary?

Calculating the size of the exponent only needs integer arithmetic like you already showed:

BigNumber(10).exponentiatedBy(15 + decimals)

That should give the same answer as the first one, right?

aress31 commented 3 years ago

Thanks for the help @shuckster but the way I did it (15 + decimals) actually output 1518 instead of 33.

Therefore, I think that the BigNumber alternative is the way to go.

shuckster commented 3 years ago

I just had a closer look at your error logs.

I think the problem is this.token.decimals(); produces a string:

const decimals = await this.token.decimals();
15 + 18
// 33

15 + "18"
// "1518"

15 + decimals
// "1518", right?

So you could fix it like this:

15 + parseInt(decimals, 10)

Or just make sure this.token.decimals(); returns a number?

aress31 commented 3 years ago

It returns a BigNumber not a string but maybe the + sign somehow force a toString() conversion, will try your version and let you know how it works. Will also try to call toNumber().

shuckster commented 3 years ago

Ah, if decimals is already a BigNumber then you should be able to do:

decimals.plus(15)
aress31 commented 3 years ago

Thanks a lot @shuckster, you helped me solved my issue. Although, I have to juggle between BN and BigNumber library which is less than optimal but BN does not seem to handle the very large numbers I am processing.

This is my solution:

  it("has a quadrilllion (10^15) initial total supply", async () => {
    const decimals = await this.token.decimals();
    const totalSupply = await this.token.totalSupply();

    expect(await this.token.totalSupply()).to.be.bignumber.equal(
      new BigNumber(10).exponentiatedBy(decimals.add(new BN(15))).toFixed()
    );
  });
shuckster commented 3 years ago

Glad you hear you fixed it!