1

I have the following two helper functions. It was based on a course I'm going through, so I didn't think I would have to debug them. I don't understand why it works with whole numbers, but not with decimals.

    export const EthToWei = (n) => {
        return new web3.utils.BN(
          web3.utils.toWei(n.toString(),'ether')
        )
      }

    export const WeiToEth = (n) => {
        return new web3.utils.BN(
          web3.utils.fromWei(n.toString(),'ether')
        )
      }

I wrote this unit test (was already using Truffle): 

    describe('testHelpers - full WeiToEth', () => {

        let wei = 2000000000000000000  // len 19 
        console.log("A1: wei=" + wei)
        let eth = WeiToEth(wei)
        console.log("A2: eth=" + eth)
        let expectedEth = 2; 
        it('full WeiToEth', async () => {
            eth.toString().should.eq(expectedEth.toString());
        })
    })

    describe('testHelpers - fraction WeiToEth', () => {

        let wei = 200000000000000000   // len 18 
        console.log("B1: wei=" + wei)
        let eth = WeiToEth(wei)
        console.log("B2: eth=" + eth)
        let expectedEth = .2; 
        it('fractional WeiToEth ', async () => {
            eth.toString().should.eq(expectedEth.toString());
        })
    })

    describe('testHelpers - Bidirectional Whole Number', () => {

        let eth = 4 
        console.log("C1: eth=" + eth)
        let wei = EthToWei(eth)
        console.log("C2: wei=" + wei)
        let eth2 = WeiToEth(wei) 
        console.log("C3: eth2=" + eth2)
        it('Bidirectional Whole Number ', async () => {
            eth2.toString().should.eq(eth.toString());
        })
    })

    describe('testHelpers - Bidirectional Decimal Number', () => {

        let eth = 4.5 
        console.log("D1: eth=" + eth)
        let wei = EthToWei(eth)
        console.log("D2: wei=" + wei)
        let eth2 = WeiToEth(wei) 
        console.log("D3: eth2=" + eth2)
        it('Bidirectional Decimal  Number ', async () => {
            eth2.toString().should.eq(eth.toString());
        })
    })

Results without the stack traces:

A1: wei=2000000000000000000
A2: eth=2
B1: wei=200000000000000000
B2: eth=-18
C1: eth=4
C2: wei=4000000000000000000
C3: eth2=4
D1: eth=4.5
D2: wei=4500000000000000000
D3: eth2=385


  testHelpers - full WeiToEth
    √ full WeiToEth

  testHelpers - fraction WeiToEth
    1) fractional WeiToEth

  testHelpers - Bidirectional Whole Number
    √ Bidirectional Whole Number

  testHelpers - Bidirectional Decimal Number
    2) Bidirectional Decimal  Number


  2 passing (57ms)
  2 failing

  1) testHelpers - fraction WeiToEth
       fractional WeiToEth :

      AssertionError: expected '-18' to equal '0.2'
      + expected - actual

      --18
      +0.2


  2) testHelpers - Bidirectional Decimal Number
       Bidirectional Decimal  Number :

      AssertionError: expected '385' to equal '4.5'
      + expected - actual

      -385
      +4.5

3 Answers 3

2

To elaborate on the previous answer - from the official documentation of BN:

Note: decimals are not supported in this library.

Here, the term decimals refers to numeric values with digits after the decimal point (i.e., non-integers).

When creating a BN object, you can pass either an integer or a string which represents an integer.

And looking at your tests, it seems that you can simply use the String result of web3.utils.fromWei rather than converting it to a BN instance and then back to a String.

If for any reason you need a non-integer big-number, then you can use either bignumber.js or decimal.js.

Just bare in mind that for interacting with your contract via web3 v1.x, you'll still need to use BN instances.


In addition to all of the above (and this too should be credited to the previous answer):

Back in the days of web3 v0.x which has relied on BigNumber, if the user passed a non-integer, then web3 would just truncate it and pass the integer value of it to the contract. Subsequently, the contract's behavior could be different than expected, and not necessarily at that exact point during execution, but possibly later. In order to protect the user from this type of scenario (which can be rather difficult to identify "on the spot"), web3 v1.x has changed this dependency and introduced the usage of BN instead of BigNumber.

1

The library web3 v1.2 uses bn.js which only support integer numbers.

In the B2 case the function WeiToEth is creating a bn.js object with an invalid input. The function toWei returns the string "0.2"

new web3.utils.BN(
    web3.utils.toWei(n.toString(),'ether')
)

Perhaps web3 tries to helps by catching the exception but it doesn't work well in your case.

1

While both the other answers were 100% correct, neither gave the solution. In my mind, I need the BigNumber when sending a value to the contract, but going from WeiToEth I don't need a big number at all.

export const EthToWei = (n) => {
    return new web3.utils.BN(
      web3.utils.toWei(n.toString(),'ether')
    )
  }

export const WeiToEth = (n) => {
     return web3.utils.fromWei(n.toString(),'ether')
  }
3
  • Didn't give solution??? Please read my answer again, as I have stated that very explicitly: "And looking at your tests, it seems that you can simply use the String result of web3.utils.fromWei rather than converting it to a BN instance and then back to a String". This is exactly what you just wrote here! Commented Nov 7, 2019 at 15:29
  • Ok, agreed. Formatting just makes it more readable. Commented Nov 7, 2019 at 15:54
  • The fromWei method can result in rounding errors. Commented Jan 16, 2020 at 21:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.