Web3JS – WeiToEth and EthToWei Conversion Issues with Decimals

web3js

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

Best Answer

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.